Lazily load channel state instead of storing in memory

finchie
Dec 10, 2025, 8:43 AM
37JT3GCX7BSR2SEC7MRTN77CIQQWR2MCAZLZSNSJZU5AIWQ2CEVQC

Dependencies

  • [2] WFWTKCJN Create initial Visual Studio Code extension
  • [3] 72K45XKD Refactor inline credit to improve hover messages
  • [4] TIPGJCUW Consistently use `EditorWorkingCopy` in `pijul-extension`
  • [5] DUZBRB3U Render correspoding hunk on inline credit hover

Change contents

  • file deletion: channel_state.rs (----------)
    [2.3681][2.45883:45923](),[2.45923][2.45924:45924]()
    use std::cell::OnceCell;
    use std::collections::HashMap;
    use libpijul::changestore::ChangeStore;
    use libpijul::pristine::TxnErr;
    use libpijul::pristine::sanakirja::{MutTxn, SanakirjaError};
    #[derive(Debug, thiserror::Error)]
    pub enum ChannelStateError<C: std::error::Error + 'static> {
    #[error("unable to iterate changes: {0:#?}")]
    ReverseLog(SanakirjaError),
    #[error("encountered error while iterating log: {0:#?}")]
    Item(SanakirjaError),
    #[error("unable to get external hash: {0:#?}")]
    InternalHash(TxnErr<SanakirjaError>),
    #[error("unable to read changes: {0:#?}")]
    ChangeStore(C),
    }
    pub struct ChannelState {
    root_change: ChangeId,
    }
    impl ChannelState {
    pub fn new<C>(
    transaction: &ArcTxn<MutTxn<()>>,
    channel: &ChannelRef<MutTxn<()>>,
    change_store: &C,
    ) -> Result<Self, ChannelStateError<C::Error>>
    where
    C: ChangeStore + Clone + Send + 'static,
    {
    let mut changes = HashMap::new();
    let mut root_change = OnceCell::new();
    // TODO: authors
    // let mut authors = HashSet::new();
    let read_transaction = transaction.read();
    let mut reverse_log = read_transaction
    .reverse_log(&*channel.read(), None)
    .map_err(ChannelStateError::ReverseLog)?
    .peekable();
    while let Some(log_result) = reverse_log.next() {
    // TODO: merkle
    let (_, (hash, merkle)) = log_result.map_err(ChannelStateError::Item)?;
    let change_id = *read_transaction
    .get_internal(hash)
    .map_err(ChannelStateError::InternalHash)?
    .unwrap();
    // TODO: `change_id.is_root()` is broken?
    if reverse_log.peek().is_none() {
    root_change.set(change_id).unwrap();
    } else {
    let change_header = change_store
    .map_err(ChannelStateError::ChangeStore)?;
    changes.try_insert(change_id, change_header).unwrap();
    }
    }
    Ok(ChannelState {
    root_change: root_change.take().unwrap(),
    changes,
    })
    }
    self.changes.get(change_id)
    }
    pub fn iter_change_headers(&self) -> impl Iterator<Item = &ChangeHeader> {
    }
    }
    self.changes.values().map(|change| &change.header)
    pub fn find_vertex_hunk(
    &self,
    vertex: Vertex<ChangeId>,
    ) -> Option<(usize, &BaseHunk<Atom<Option<Hash>>, Local>)> {
    let change = self.changes.get(&vertex.change)?;
    change.changes.iter().enumerate().find_map(|(index, hunk)| {
    if hunk.iter().any(|atom| match atom {
    Atom::NewVertex(new_vertex) => {
    // Changes that depend on this one may later fragment the original contents,
    // so the current vertex may be a subset of the original contents.
    new_vertex.start <= vertex.start && new_vertex.end >= vertex.end
    }
    Atom::EdgeMap(_edge_map) => false,
    }) {
    // Hunks are 1-indexed
    Some((index + 1, hunk))
    } else {
    None
    }
    })
    }
    pub fn get_change(&self, change_id: &ChangeId) -> Option<&Change> {
    .get_change(&hash.into())
    // TODO: lazy access from ChangeStore?
    changes: HashMap<ChangeId, Change>,
    use libpijul::{ArcTxn, ChangeId, ChannelRef, GraphTxnT, Hash, TxnTExt, Vertex};
    use libpijul::change::{Atom, BaseHunk, Change, ChangeHeader, Local};
  • edit in pijul-extension/src/lib.rs at line 14
    [2.10812]
    [2.10812]
    use std::borrow::Cow;
  • replacement in pijul-extension/src/lib.rs at line 24
    [2.11192][5.77:137]()
    use libpijul::{ArcTxn, ChangeId, ChannelRef, TxnT, Vertex};
    [2.11192]
    [4.0]
    use libpijul::{ArcTxn, Base32, ChangeId, ChannelRef, GraphTxnT, TxnT, Vertex};
  • replacement in pijul-extension/src/lib.rs at line 27
    [2.11245][2.11245:11350]()
    use crate::author::{AuthorSource, Authors};
    use crate::channel_state::{ChannelState, ChannelStateError};
    [2.11245]
    [5.138]
    use crate::author::{AuthorSource, Authors, GetAuthorsError};
  • edit in pijul-extension/src/lib.rs at line 39
    [2.11602][2.11602:11625]()
    pub mod channel_state;
  • replacement in pijul-extension/src/lib.rs at line 51
    [2.12111][2.12111:12200]()
    #[error("failed to log changes: {0:#?}")]
    Changes(#[from] ChannelStateError<C>),
    [2.12111]
    [2.12200]
    #[error("failed to get local authors: {0:#?}")]
    Changes(#[from] GetAuthorsError<C>),
  • edit in pijul-extension/src/lib.rs at line 66
    [2.12595]
    [2.12595]
    pub enum GetChangeError<C: std::error::Error + 'static> {
    #[error("Unable to begin transaction: {0}")]
    Transaction(#[from] SanakirjaError),
    #[error("Unable to get external hash: {0}")]
    Hash(#[from] TxnErr<SanakirjaError>),
    #[error("Failed to get change: {0}")]
    Change(C),
    #[error("No matching change for {change_id:?}")]
    NoMatchingChange { change_id: ChangeId },
    }
    #[derive(Debug, thiserror::Error)]
    pub enum FindVertexHunkError<C: std::error::Error + 'static> {
    #[error("Unable to get change: {0}")]
    Change(#[from] GetChangeError<C>),
    #[error("No hunk found for vertex: {vertex:?}")]
    NoHunkForVertex { vertex: Vertex<ChangeId> },
    }
    #[derive(Debug, thiserror::Error)]
  • replacement in pijul-extension/src/lib.rs at line 104
    [5.580][5.580:775]()
    #[error("Change not found: {change_id:?}")]
    ChangeNotFound { change_id: ChangeId },
    #[error("No hunk found for vertex: {vertex:?}")]
    NoHunkForVertex { vertex: Vertex<ChangeId> },
    [5.580]
    [5.775]
    #[error("Failed to get change for {change_id:?}: {error}")]
    Change {
    change_id: ChangeId,
    error: GetChangeError<C>,
    },
    #[error("Unable to find hunk for vertex: {0}")]
    FindVertexHunk(#[from] FindVertexHunkError<C>),
  • edit in pijul-extension/src/lib.rs at line 165
    [2.14040][2.14040:14077]()
    pub channel_state: ChannelState,
  • edit in pijul-extension/src/lib.rs at line 186
    [2.14890][2.14890:14891]()
  • edit in pijul-extension/src/lib.rs at line 187
    [2.14955]
    [2.14955]
    let authors = Authors::new(&dot_directory, &transaction, &channel, &change_store)?;
  • edit in pijul-extension/src/lib.rs at line 189
    [2.14956][2.14956:15112]()
    let channel_state = ChannelState::new(&transaction, &channel, &change_store)?;
    let authors = Authors::new(&channel_state, &dot_directory);
  • edit in pijul-extension/src/lib.rs at line 200
    [2.15456][2.15456:15483]()
    channel_state,
  • replacement in pijul-extension/src/lib.rs at line 214
    [2.15761][5.1082:1203]()
    pub fn get_change(&self, change_id: &ChangeId) -> Option<&Change> {
    self.channel_state.get_change(change_id)
    [2.15761]
    [5.1203]
    pub fn get_change(&self, change_id: ChangeId) -> Result<Change, GetChangeError<C::Error>> {
    let transaction = self.pristine.arc_txn_begin()?;
    let read_transaction = transaction.read();
    let external_hash = read_transaction
    .get_external(&change_id)?
    .ok_or(GetChangeError::NoMatchingChange { change_id })?;
    self.change_store
    .get_change(&external_hash.into())
    .map_err(GetChangeError::Change)
    }
    pub fn find_vertex_hunk(
    &self,
    vertex: Vertex<ChangeId>,
    ) -> Result<
    (
    usize,
    BaseHunk<Atom<Option<libpijul::Hash>>, libpijul::change::Local>,
    ),
    FindVertexHunkError<C::Error>,
    > {
    let change = self.get_change(vertex.change)?;
    change
    .hashed
    .changes
    .into_iter()
    .enumerate()
    .find_map(|(index, hunk)| {
    hunk.iter()
    .any(|atom| match atom {
    Atom::NewVertex(new_vertex) => {
    // Changes that depend on this one may later fragment the original contents,
    // so the current vertex may be a subset of the original contents.
    new_vertex.start <= vertex.start && new_vertex.end >= vertex.end
    }
    Atom::EdgeMap(_edge_map) => false,
    })
    // Hunks are 1-indexed
    .then_some((index + 1, hunk))
    })
    .ok_or(FindVertexHunkError::NoHunkForVertex { vertex })
  • replacement in pijul-extension/src/lib.rs at line 271
    [5.1605][5.1605:1851]()
    let Some(change) = self.channel_state.get_change(&vertex.change) else {
    return Err(ActiveHunkError::ChangeNotFound {
    change_id: vertex.change,
    });
    };
    [5.1605]
    [5.1851]
    let change =
    self.get_change(vertex.change)
    .map_err(|error| ActiveHunkError::Change {
    change_id: vertex.change,
    error,
    })?;
  • replacement in pijul-extension/src/lib.rs at line 278
    [5.1852][5.1852:1885]()
    &change.contents
    [5.1852]
    [5.1885]
    Cow::Owned(change.contents)
    }
    CreditSource::Untracked { hunk_index: _ } => {
    Cow::Borrowed(&credits.unrecorded_state.change_contents)
  • edit in pijul-extension/src/lib.rs at line 283
    [5.1899][5.1899:1999]()
    CreditSource::Untracked { hunk_index: _ } => &credits.unrecorded_state.change_contents,
  • replacement in pijul-extension/src/lib.rs at line 286
    [5.2066][5.2066:2270]()
    CreditSource::Tracked { vertex } => self
    .channel_state
    .find_vertex_hunk(vertex)
    .ok_or_else(|| ActiveHunkError::NoHunkForVertex { vertex })?,
    [5.2066]
    [5.2270]
    CreditSource::Tracked { vertex } => {
    let (index, hunk) = self.find_vertex_hunk(vertex)?;
    (index, Cow::Owned(hunk))
    }
  • replacement in pijul-extension/src/lib.rs at line 292
    [5.2368][5.2368:2446]()
    (hunk_index + 1, &credits.unrecorded_state.hunks[hunk_index])
    [5.2368]
    [5.2446]
    (
    hunk_index + 1,
    Cow::Borrowed(&credits.unrecorded_state.hunks[hunk_index]),
    )
  • replacement in pijul-extension/src/lib.rs at line 300
    [5.2514][5.2514:2551]()
    let hunk_diff = match hunk {
    [5.2514]
    [5.2551]
    let hunk_diff = match hunk.as_ref() {
  • replacement in pijul-extension/src/lib.rs at line 310
    [5.2861][5.2861:2915]()
    hunk: Box::new(hunk.clone()),
    [5.2861]
    [5.2915]
    hunk: Box::new(hunk.into_owned()),
  • replacement in pijul-extension/src/lib.rs at line 317
    [5.3104][5.3104:3141]()
    change_contents,
    [5.3104]
    [5.3141]
    &change_contents,
  • replacement in pijul-extension/src/lib.rs at line 335
    [5.3739][5.3739:3776]()
    change_contents,
    [5.3739]
    [5.3776]
    &change_contents,
  • replacement in pijul-extension/src/lib.rs at line 340
    [5.3943][5.3943:3980]()
    change_contents,
    [5.3943]
    [5.3980]
    &change_contents,
  • replacement in pijul-extension/src/lib.rs at line 359
    [5.4649][5.4649:4686]()
    change_contents,
    [5.4649]
    [5.4686]
    &change_contents,
  • replacement in pijul-extension/src/lib.rs at line 380
    [5.5463][5.5463:5513]()
    hunk: Box::new(hunk.clone()),
    [5.5463]
    [5.5513]
    hunk: Box::new(hunk.into_owned()),
  • replacement in pijul-extension/src/author.rs at line 7
    [2.48765][2.48765:48806]()
    use crate::channel_state::ChannelState;
    [2.48765]
    [2.48806]
    use libpijul::changestore::ChangeStore;
    use libpijul::pristine::SerializedHash;
    use libpijul::pristine::sanakirja::{MutTxn, SanakirjaError};
    use libpijul::{ArcTxn, ChannelRef, TxnTExt};
  • edit in pijul-extension/src/author.rs at line 17
    [2.48951]
    [2.48951]
    }
    #[derive(Debug, thiserror::Error)]
    pub enum GetAuthorsError<C: std::error::Error + 'static> {
    #[error("Unable to get log of changes: {0}")]
    Log(SanakirjaError),
    #[error("Error while iterating log: {0}")]
    LogEntry(SanakirjaError),
    #[error("Failed to get change header for {hash:?}: {error}")]
    ChangeHeader { hash: SerializedHash, error: C },
  • replacement in pijul-extension/src/author.rs at line 34
    [2.49037][2.49037:49235]()
    pub fn new(channel_state: &ChannelState, dot_directory: &Utf8Path) -> Self {
    let mut authors = HashMap::new();
    let repository_identities_path = dot_directory.join("identities");
    [2.49037]
    [2.49235]
    pub fn new<C>(
    dot_directory: &Utf8Path,
    transaction: &ArcTxn<MutTxn<()>>,
    channel: &ChannelRef<MutTxn<()>>,
    change_store: &C,
    ) -> Result<Self, GetAuthorsError<C::Error>>
    where
    C: ChangeStore + Clone + Send + 'static,
    {
    let mut authors = Self {
    authors: HashMap::new(),
    };
    // Get local identities
    authors.insert_local_identities();
    // Get identities stored in the repository
    authors.insert_repository_identities(dot_directory, transaction, channel, change_store)?;
    Ok(authors)
    }
  • edit in pijul-extension/src/author.rs at line 56
    [2.49236]
    [2.49236]
    fn insert_local_identities(&mut self) {
  • replacement in pijul-extension/src/author.rs at line 62
    [2.49471][2.49471:49543]()
    match authors.entry(public_key_signature.clone()) {
    [2.49471]
    [2.49543]
    match self.authors.entry(public_key_signature.clone()) {
  • edit in pijul-extension/src/author.rs at line 78
    [2.50240]
    [2.50240]
    }
  • replacement in pijul-extension/src/author.rs at line 80
    [2.50241][2.50241:50308]()
    for change_header in channel_state.iter_change_headers() {
    [2.50241]
    [2.50308]
    fn insert_repository_identities<C>(
    &mut self,
    dot_directory: &Utf8Path,
    transaction: &ArcTxn<MutTxn<()>>,
    channel: &ChannelRef<MutTxn<()>>,
    change_store: &C,
    ) -> Result<(), GetAuthorsError<C::Error>>
    where
    C: ChangeStore + Clone + Send + 'static,
    {
    let repository_identities_path = dot_directory.join("identities");
    let read_transaction = transaction.read();
    let log_entries = read_transaction
    .log(&*channel.read(), 0)
    .map_err(GetAuthorsError::Log)?;
    for log_entry in log_entries {
    let (_index, (serialized_hash, _merkle)) =
    log_entry.map_err(GetAuthorsError::LogEntry)?;
    let change_header =
    change_store
    .get_header(&serialized_hash.into())
    .map_err(|error| GetAuthorsError::ChangeHeader {
    hash: *serialized_hash,
    error,
    })?;
  • replacement in pijul-extension/src/author.rs at line 112
    [2.50538][2.50538:50613]()
    authors.entry(public_key_signature.to_owned())
    [2.50538]
    [2.50613]
    self.authors.entry(public_key_signature.to_owned())
  • replacement in pijul-extension/src/author.rs at line 152
    [2.52462][2.52462:52487]()
    Self { authors }
    [2.52462]
    [2.52487]
    Ok(())
  • replacement in extensions/vscode/src/inline_credit/line_annotation.rs at line 28
    [2.124314][2.124314:124352]()
    message: &'change_header str,
    [2.124314]
    [2.124352]
    message: String,
  • replacement in extensions/vscode/src/inline_credit/line_annotation.rs at line 62
    [5.9591][5.9591:9766]()
    let change = repository.get_change(&vertex.change).ok_or_else(|| {
    napi::Error::from_reason(format!("Unable to get change for verte {vertex:#?}"))
    [5.9591]
    [3.5559]
    let change = repository.get_change(vertex.change).map_err(|error| {
    napi::Error::from_reason(format!(
    "Unable to get change for vertex {vertex:#?}: {error}"
    ))
  • replacement in extensions/vscode/src/inline_credit/line_annotation.rs at line 87
    [5.9874][5.9874:9923]()
    message: &change.header.message,
    [5.9874]
    [2.126772]
    message: change.hashed.header.message,
  • replacement in extensions/vscode/src/inline_credit/hover.rs at line 87
    [5.11263][5.11263:11440]()
    let change = repository.get_change(&vertex.change).ok_or_else(|| {
    napi::Error::from_reason(format!("Unable to get change for vertex: {vertex:#?}"))
    [5.11263]
    [3.8663]
    let change = repository.get_change(vertex.change).map_err(|error| {
    napi::Error::from_reason(format!(
    "Unable to get change for vertex {vertex:#?}: {error}"
    ))