add pull

[?]
Dec 3, 2025, 8:10 PM
ODCT4QJNJLQTDNFPIF7HX4XCFTEXZBESG3PTD276O7TWB7MSGWMQC

Dependencies

  • [2] D7A7MSIH allow to defer or abandon record, add buttons
  • [3] OQ6HSAWH show record log
  • [4] ACDXXAX2 refactor main's updates into smaller fns
  • [5] KWTBNTO3 diffs selection and scrolling
  • [6] WAOGSCOJ very nice refactor, wip adding channels logs
  • [7] EJPSD5XO shared allowed actions conditions between update and view
  • [8] YK3MOJJL chonky refactor, wip other channels logs & diffs
  • [9] IFQPVMBD error handling for repo actions
  • [10] LFEMJYYD start of to_record selection
  • [11] YGZ3VCW4 add push
  • [12] UF5NJKAS test load repo
  • [*] SWWE2R6M display basic repo stuff
  • [*] 6YZAVBWU Initial commit

Change contents

  • replacement in libflorescence/src/repo.rs at line 8
    [5.56][11.77:120]()
    use pijul_remote::{PushDelta, RemoteRepo};
    [5.56]
    [11.120]
    use pijul_remote::{PushDelta, RemoteDelta, RemoteRepo};
  • edit in libflorescence/src/repo.rs at line 78
    [11.148]
    [6.580]
    Pull,
  • edit in libflorescence/src/repo.rs at line 108
    [11.183]
    [11.183]
    result: anyhow::Result<()>,
    },
    Pulled {
  • edit in libflorescence/src/repo.rs at line 459
    [11.2022]
    [11.2022]
    }
    MsgIn::Pull => {
    let result = pull(&internal_state.path).await;
    let _ = msg_out_tx.send(MsgOut::Pulled { result });
  • edit in libflorescence/src/repo.rs at line 1286
    [11.4665]
    [11.4665]
    }
    }
    #[allow(
    clippy::await_holding_lock,
    reason = "imposed by sanakirja API for txn"
    )]
    async fn pull(repo_path: &Path) -> Result<(), anyhow::Error> {
    let mut repo = pijul::Repository::find_root(Some(repo_path))?;
    let channel_name = current_channel(&repo)?;
    let txn = repo.pristine.arc_txn_begin()?;
    let mut channel = txn.write().open_or_create_channel(&channel_name)?;
    let remote_name = if let Some(ref def) = repo.config.default_remote {
    def
    } else {
    bail!("Missing remote")
    };
    // TODO why no same as current channel_name?
    let from_channel = libpijul::DEFAULT_CHANNEL;
    let no_cert_check = false;
    let mut remote = pijul_remote::repository(
    &repo,
    Some(&repo.path),
    None,
    remote_name,
    from_channel,
    no_cert_check,
    true,
    )
    .await?;
    let RemoteDelta {
    inodes,
    remote_ref,
    to_download,
    remote_unrecs: _,
    ..
    } = to_download(&mut txn.write(), &mut channel, &mut repo, &mut remote)
    .await?;
    let hash = pending(txn.clone(), &channel, &mut repo)?;
    if let Some(ref r) = remote_ref {
    remote.update_identities(&mut repo, r).await?;
    }
    // TODO: Show remote unrecords
    // notify_remote_unrecords(&repo, remote_unrecs.as_slice());
    if to_download.is_empty() {
    if let Some(ref h) = hash {
    txn.write().unrecord(&repo.changes, &channel, h, 0)?;
    }
    txn.commit()?;
    bail!("Nothing to pull");
    }
    {
    // Now that .pull is always given `false` for `do_apply`...
    let mut ws = libpijul::ApplyWorkspace::new();
    debug!("to_download = {:#?}", to_download);
    let mut channel = channel.write();
    let mut txn = txn.write();
    for h in to_download.iter().rev() {
    match h {
    pijul_remote::CS::Change(h) => {
    txn.apply_change_rec_ws(
    &repo.changes,
    &mut channel,
    h,
    &mut ws,
    )?;
    }
    pijul_remote::CS::State(s) => {
    if let Some(n) =
    txn.channel_has_state(&channel.states, &s.into())?
    {
    txn.put_tags(&mut channel.tags, n.into(), s)?;
    } else {
    bail!(
    "Cannot add tag {}: channel {:?} does not have that state",
    libpijul::Base32::to_base32(s),
    channel.name
    )
    }
    }
    }
    }
    }
    let full = false;
    remote
    .complete_changes(&repo, &*txn.read(), &mut channel, &to_download, full)
    .await?;
    remote.finish().await?;
    debug!("inodes = {:?}", inodes);
    debug!("to_download: {:?}", to_download.len());
    let mut touched = HashSet::new();
    let txn_ = txn.read();
    for d in to_download.iter() {
    debug!("to_download {:?}", d);
    match d {
    pijul_remote::CS::Change(d) => {
    use pijul::GraphTxnT;
    if let Some(int) = txn_.get_internal(&d.into())? {
    use pijul::DepsTxnT;
    for inode in txn_.iter_rev_touched(int)? {
    let (int_, inode) = inode?;
    if int_ < int {
    continue;
    } else if int_ > int {
    break;
    }
    use pijul::GraphTxnT;
    let ext = libpijul::pristine::Position {
    change: txn_
    .get_external(&inode.change)?
    .unwrap()
    .into(),
    pos: inode.pos,
    };
    if inodes.is_empty() || inodes.contains(&ext) {
    touched.insert(*inode);
    }
    }
    }
    }
    pijul_remote::CS::State(_) => {
    // No need to do anything for now here, we don't
    // output after downloading a tag.
    }
    }
    }
    std::mem::drop(txn_);
    let is_current_channel = true;
    if is_current_channel {
    let mut touched_paths = BTreeSet::new();
    {
    let txn_ = txn.read();
    for &i in touched.iter() {
    if let Some((path, _)) = libpijul::fs::find_path(
    &repo.changes,
    &*txn_,
    &*channel.read(),
    false,
    i,
    )? {
    touched_paths.insert(path);
    } else {
    touched_paths.clear();
    break;
    }
    }
    }
    if touched_paths.is_empty() {
    touched_paths.insert(String::from(""));
    }
    let mut last: Option<&str> = None;
    let mut conflicts = Vec::new();
    for path in touched_paths.iter() {
    if let Some(last_path) = last {
    // If `last_path` is a prefix (in the path sense) of `path`,
    // skip.
    if last_path.len() < path.len() {
    let (pre_last, post_last) = path.split_at(last_path.len());
    if pre_last == last_path && post_last.starts_with("/") {
    continue;
    }
    }
    }
    debug!("path = {:?}", path);
    conflicts.extend(
    libpijul::output::output_repository_no_pending(
    &repo.working_copy,
    &repo.changes,
    &txn,
    &channel,
    path,
    true,
    None,
    std::thread::available_parallelism()?.get(),
    0,
    )?
    .into_iter(),
    );
    last = Some(path)
    }
    // TODO: handle conflicts
    // print_conflicts(&conflicts)?;
    }
    if let Some(h) = hash {
    txn.write().unrecord(&repo.changes, &channel, &h, 0)?;
    repo.changes.del_change(&h)?;
  • edit in libflorescence/src/repo.rs at line 1484
    [11.4671]
    [11.4671]
    txn.commit()?;
    Ok(())
  • edit in libflorescence/src/repo.rs at line 1489
    [11.4674]
    [3.2457]
    /// Gets the `to_download` vec and calculates any remote unrecords.
    /// If the local remote cache can be auto-updated, it will be.
    async fn to_download(
    txn: &mut MutTxn<()>,
    channel: &mut ChannelRef<MutTxn<()>>,
    repo: &mut pijul::Repository,
    remote: &mut RemoteRepo,
    ) -> Result<RemoteDelta<MutTxn<()>>, anyhow::Error> {
    let path = &[];
    let force_cache = None;
    let changes = &[];
    let delta = remote
    .update_changelist_pushpull(
    txn,
    path,
    channel,
    force_cache,
    repo,
    changes,
    true,
    )
    .await?;
    let to_download = remote
    .pull(
    repo,
    txn,
    channel,
    delta.to_download.as_slice(),
    &delta.inodes,
    false,
    )
    .await?;
    Ok(RemoteDelta {
    to_download,
    ..delta
    })
    }
    /// Record the pending change (i.e. any unrecorded modifications in
    /// the working copy), returning its hash.
    fn pending<T: pijul::MutTxnTExt + pijul::TxnT + Send + Sync + 'static>(
    txn: pijul::ArcTxn<T>,
    channel: &pijul::ChannelRef<T>,
    repo: &mut pijul_repository::Repository,
    ) -> anyhow::Result<Option<libpijul::Hash>> {
    use libpijul::changestore::ChangeStore;
    let mut builder = pijul::record::Builder::new();
    builder.record(
    txn.clone(),
    libpijul::Algorithm::default(),
    false,
    &libpijul::DEFAULT_SEPARATOR,
    channel.clone(),
    &repo.working_copy,
    &repo.changes,
    "",
    std::thread::available_parallelism()?.get(),
    )?;
    let recorded = builder.finish();
    if recorded.actions.is_empty() {
    return Ok(None);
    }
    let mut txn = txn.write();
    let actions = recorded
    .actions
    .into_iter()
    .map(|rec| rec.globalize(&*txn).unwrap())
    .collect();
    let contents = if let Ok(c) = std::sync::Arc::try_unwrap(recorded.contents)
    {
    c.into_inner()
    } else {
    unreachable!()
    };
    let mut pending_change = libpijul::change::Change::make_change(
    &*txn,
    channel,
    actions,
    contents,
    libpijul::change::ChangeHeader::default(),
    Vec::new(),
    )?;
    let (dependencies, extra_known) = libpijul::change::dependencies(
    &*txn,
    &*channel.read(),
    pending_change.changes.iter(),
    )?;
    pending_change.dependencies = dependencies;
    pending_change.extra_known = extra_known;
    let hash = repo
    .changes
    .save_change(&mut pending_change, |_, _| Ok::<_, anyhow::Error>(()))
    .unwrap();
    txn.apply_local_change(
    channel,
    &pending_change,
    &hash,
    &recorded.updatables,
    )?;
    Ok(Some(hash))
    }
  • edit in inflorescence_model/src/action.rs at line 41
    [11.4820]
    [7.4320]
    Pull,
  • edit in inflorescence_model/src/action.rs at line 104
    [11.4851]
    [7.5127]
    (Pull, Pull) => true,
  • edit in inflorescence_model/src/action.rs at line 126
    [11.4880]
    [7.5587]
    (Pull, _) => false,
  • edit in inflorescence_model/src/action.rs at line 347
    [11.4995]
    [7.8943]
    let pull = || Binding {
    key: "F",
    label: "pull",
    msg: Some(FilteredMsg::Pull),
    };
  • edit in inflorescence_model/src/action.rs at line 396
    [11.5025]
    [7.10048]
    ma.push(pull());
  • edit in inflorescence/src/main.rs at line 557
    [11.5615]
    [11.5615]
    // TODO switch state while pushing to display progress
  • edit in inflorescence/src/main.rs at line 561
    [10.18195]
    [8.60824]
    action::FilteredMsg::Pull => {
    // TODO switch state while pulling to display progress
    state.repo_tx_in.send(repo::MsgIn::Pull).unwrap();
    Task::none()
    }
  • edit in inflorescence/src/main.rs at line 756
    [9.13331]
    [4.1200]
    repo::MsgOut::Pulled { result } => match result {
    Ok(()) => Task::none(),
    Err(err) => report_err(state, err.to_string()),
    },
  • edit in inflorescence/src/main.rs at line 1568
    [2.2363]
    [7.29022]
    "f" if mods == Modifiers::SHIFT => {
    action(action::FilteredMsg::Pull)
    }