Handle named remotes in pijul remote and remote delete

dblsaiko
Mar 27, 2024, 9:21 PM
67GIAQEUQG3KUD7YTYNUWK33BKWPFVNT4YPQMZ3RCALOZ2STDLRQC

Dependencies

  • [2] EEF5WHUI Print default remote if set but not registered in the database
  • [3] A3RM526Y Integrating identity malleability
  • [4] IVLLXQ5Z Improved push/pull reporting
  • [5] SXEYMYF7 Fixing the bad changes in history (unfortunately, by rebooting).
  • [6] X243Z3Y5 Recording only the required metadata (can even be changed later!)
  • [7] CCLLB7OI Upgrading to Sanakirja 0.15 + version bump
  • [8] ZBNKSYA6 Fixing a bus error when starting a transaction on a full disk
  • [9] EUZFFJSO Updating Pijul with the latest changes in Libpijul
  • [10] YN63NUZO Sanakirja 1.0
  • [11] JL4WKA5P Implement the Sanakirja concurrency model in a cross-process way
  • [12] 3KRGVQFU Do not update the mtime of unmodified files
  • [13] L4JXJHWX pijul/*: reorganize imports and remove extern crate
  • [14] LGEJSLTY Fixing output (including its uses in reset and pull)
  • [15] AEPEFS7O Write help for each argument
  • [16] 6ZPDI7QG pull uses None as the base case path when outputing repo
  • [17] OWJL5HO7 Allow deleting remote by URL and remove default remote if set to deleted remote
  • [18] VZL5OHF5 Remove the obsolete `remote list` command
  • [19] QCPIBC6M Make the default remote configurable through the cli
  • [*] H4AU6QRP New config for HTTP remotes
  • [*] ZSFJT4SF Allow remotes to have a different push and pull address

Change contents

  • edit in pijul-config/src/lib.rs at line 186
    [21.4092]
    [22.794]
    }
    }
    pub fn url(&self) -> &str {
    match self {
    RemoteConfig::Ssh { ssh, .. } => ssh,
    RemoteConfig::Http { http, .. } => http,
  • edit in pijul-config/src/lib.rs at line 195
    [22.810]
    [22.810]
    pub fn db_uses_name(&self) -> bool {
    match self {
    RemoteConfig::Ssh { .. } => false,
    RemoteConfig::Http { .. } => true,
    }
    }
  • replacement in pijul/src/commands/pushpull.rs at line 1
    [3.111631][3.0:43](),[3.43][3.1651:1671](),[3.255][3.1651:1671](),[3.757][3.1651:1671](),[3.1651][3.1651:1671]()
    use std::collections::{BTreeSet, HashSet};
    use std::io::Write;
    [3.111631]
    [3.1671]
    use std::collections::{BTreeMap, BTreeSet, HashSet};
    use std::io;
    use std::io::{Stdout, Write};
  • edit in pijul/src/commands/pushpull.rs at line 13
    [3.34][3.34:75]()
    use libpijul::small_string::SmallString;
  • edit in pijul/src/commands/pushpull.rs at line 15
    [3.1759]
    [3.111785]
    use pijul_config::{RemoteConfig, RemoteHttpHeader};
  • replacement in pijul/src/commands/pushpull.rs at line 38
    [3.112118][3.112118:112149]()
    Delete { remote: String },
    [3.112118]
    [3.112186]
    Delete {
    remote: String,
    /// Delete only the specified datum, i.e. database ID or remote name. If this isn't specified and there is at most one of each kind of data linked to a remote, the entire remote is deleted.
    #[arg(group = "behavior", short = '1', long = "exact")]
    exact: bool,
    /// Delete all the linked data of the remote, i.e. all names and all database IDs.
    #[arg(group = "behavior", long = "flood")]
    flood: bool,
    },
  • edit in pijul/src/commands/pushpull.rs at line 49
    [3.112189]
    [3.76]
    #[derive(Default)]
  • replacement in pijul/src/commands/pushpull.rs at line 51
    [3.96][3.96:145]()
    id: Option<RemoteId>,
    path: SmallString,
    [3.96]
    [3.145]
    id: BTreeSet<RemoteId>,
    path: String,
    configs: BTreeMap<String, ExtraConfig>,
    default: bool,
    }
    struct RemoteInfos {
    data: Vec<RemoteInfo>,
    by_id: BTreeMap<RemoteId, usize>,
    by_url: BTreeMap<String, usize>,
    by_label: BTreeMap<String, usize>,
    }
    struct ExtraConfig {
    headers: BTreeMap<String, String>,
    default: bool,
  • replacement in pijul/src/commands/pushpull.rs at line 69
    [3.148][3.148:284](),[3.284][3.284:472]()
    fn get_remote_by_any(
    repo: &Repository,
    txn: &mut MutTxn<()>,
    spec: &str,
    ) -> Result<Option<RemoteInfo>, anyhow::Error> {
    // Try indexing by ID first
    if let Some(id) = RemoteId::from_base32(spec.as_bytes()) {
    if let Some(r) = txn.get_remote(id)? {
    let path = r.lock().path.clone();
    [3.148]
    [3.472]
    /// Aggregates remote information from three sources:
    /// - the repo database
    /// - named remotes in the configuration
    /// - the default remote set in the configuration
    fn aggregate_remote_info<T>(repo: &Repository, txn: &T) -> Result<RemoteInfos, anyhow::Error>
    where
    T: TxnT,
    {
    let mut data = Vec::new();
    let mut by_id = BTreeMap::new();
    let mut by_url = BTreeMap::new();
    let mut by_label = BTreeMap::new();
    let mut by_db_name = BTreeMap::new();
  • replacement in pijul/src/commands/pushpull.rs at line 83
    [3.473][3.473:537]()
    return Ok(Some(RemoteInfo { id: Some(id), path }));
    [3.473]
    [3.537]
    for rc in &repo.config.remotes {
    let idx = by_url.get(rc.url()).copied().unwrap_or_else(|| {
    data.push(RemoteInfo {
    path: rc.url().to_string(),
    ..Default::default()
    });
    data.len() - 1
    });
    if rc.db_uses_name() {
    by_db_name.insert(rc.name().to_string(), idx);
  • edit in pijul/src/commands/pushpull.rs at line 95
    [3.547]
    [3.547]
    by_db_name.insert(rc.url().to_string(), idx);
    by_label.insert(rc.name().to_string(), idx);
    by_url.insert(rc.url().to_string(), idx);
    let d = &mut data[idx];
    let headers = match rc {
    RemoteConfig::Ssh { .. } => BTreeMap::new(),
    RemoteConfig::Http { headers, .. } => headers
    .iter()
    .map(|(k, v)| {
    let v = match v {
    RemoteHttpHeader::String(s) => s.clone(),
    RemoteHttpHeader::Shell(s) => s.shell.clone(),
    };
    (k.clone(), v)
    })
    .collect(),
    };
    d.configs.insert(
    rc.name().to_string(),
    ExtraConfig {
    headers,
    // set later
    default: false,
    },
    );
  • edit in pijul/src/commands/pushpull.rs at line 126
    [3.554][3.554:579]()
    // Look it up by URL
  • replacement in pijul/src/commands/pushpull.rs at line 128
    [3.650][3.650:692]()
    let path = r.lock().path.clone();
    [3.650]
    [3.692]
    let lock = r.lock();
  • replacement in pijul/src/commands/pushpull.rs at line 130
    [3.693][3.693:852]()
    if path.as_str() == spec {
    return Ok(Some(RemoteInfo {
    id: Some(*r.id()),
    path,
    }));
    }
    [3.693]
    [3.852]
    let idx = by_db_name
    .get(lock.path.as_str())
    .copied()
    .unwrap_or_else(|| {
    data.push(RemoteInfo {
    path: lock.path.as_str().to_string(),
    ..Default::default()
    });
    by_url.insert(lock.path.as_str().to_string(), data.len() - 1);
    data.len() - 1
    });
    data[idx].id.insert(*r.id());
    by_id.insert(*r.id(), idx);
  • replacement in pijul/src/commands/pushpull.rs at line 147
    [3.859][3.859:1089]()
    // Otherwise, is it set as the default remote?
    if repo.config.default_remote.as_deref() == Some(spec) {
    return Ok(Some(RemoteInfo {
    id: None,
    path: SmallString::from_str(spec),
    }));
    [3.859]
    [3.1089]
    if let Some(default) = &repo.config.default_remote {
    let label_idx = by_label.get(default);
    let idx = label_idx
    .or_else(|| by_url.get(default))
    .copied()
    .unwrap_or_else(|| {
    data.push(RemoteInfo {
    path: default.to_string(),
    ..Default::default()
    });
    by_url.insert(default.to_string(), data.len() - 1);
    data.len() - 1
    });
    data[idx].default = true;
    if label_idx.is_some() {
    for (label, ec) in data[idx].configs.iter_mut() {
    if label == default {
    ec.default = true;
    }
    }
    }
  • replacement in pijul/src/commands/pushpull.rs at line 174
    [3.1096][3.1096:1109]()
    Ok(None)
    [3.1096]
    [3.1109]
    Ok(RemoteInfos {
    data,
    by_id,
    by_url,
    by_label,
    })
  • replacement in pijul/src/commands/pushpull.rs at line 186
    [3.112351][3.112351:112395]()
    let mut stdout = std::io::stdout();
    [3.112351]
    [3.112395]
    let mut stdout = io::stdout();
  • replacement in pijul/src/commands/pushpull.rs at line 190
    [3.112523][2.0:49]()
    let mut printed_default = false;
    [3.112523]
    [2.49]
    let remote_infos = aggregate_remote_info(&repo, &txn)?;
  • replacement in pijul/src/commands/pushpull.rs at line 192
    [2.50][3.1113:1176](),[3.112523][3.1113:1176](),[3.1176][3.12924:12956](),[3.14736][3.12924:12956](),[3.12924][3.12924:12956](),[3.12956][2.51:92]()
    for r in txn.iter_remotes(&RemoteId::nil())? {
    let r = r?;
    let lock = r.lock();
    [2.50]
    [2.92]
    for info in remote_infos.data {
    let can_collapse = info.configs.len() < 2
    || info.configs.iter().all(|(_, el)| el.headers.is_empty());
  • replacement in pijul/src/commands/pushpull.rs at line 197
    [2.133][2.133:272]()
    if Some(lock.path.as_str()) == repo.config.default_remote.as_deref() {
    printed_default = true;
    [2.133]
    [2.272]
    if info.default
    && (can_collapse || info.configs.iter().all(|(_, c)| !c.default))
    {
  • replacement in pijul/src/commands/pushpull.rs at line 203
    [2.331][2.331:417](),[2.417][3.112628:112646](),[3.6630][3.112628:112646](),[3.14829][3.112628:112646](),[3.112628][3.112628:112646]()
    writeln!(stdout, "{} {}: {}", flag, r.id(), lock.path.as_str())?;
    }
    [2.331]
    [2.418]
    // Under normal circumstances, there should only be at most
    // one ID here. However, still do our best to format it in a
    // reasonably nice way.
    let mut ids = info.id.iter();
    write!(stdout, "{} ", flag)?;
  • replacement in pijul/src/commands/pushpull.rs at line 210
    [2.419][2.419:600]()
    if !printed_default {
    if let Some(path) = repo.config.default_remote {
    writeln!(stdout, "* {:26}: {}", "(none)", path)?;
    [2.419]
    [2.600]
    if let Some(id) = ids.next() {
    write!(stdout, "{}", id)?;
    } else {
    write!(stdout, "{:26}", "(no ID)")?;
    }
    write!(stdout, ": ")?;
    fn write_headers(stdout: &mut Stdout, c: &ExtraConfig) -> io::Result<()> {
    if !c.headers.is_empty() {
    writeln!(stdout, " Headers:")?;
    for (header, value) in &c.headers {
    writeln!(stdout, " - {}: {}", header, value)?;
    }
    }
    Ok(())
    }
    if can_collapse {
    for (name, _) in &info.configs {
    write!(stdout, "«{}» ", name)?;
    }
    writeln!(stdout, "{}", info.path)?;
    for (_, c) in &info.configs {
    write_headers(&mut stdout, c)?;
    }
    } else {
    writeln!(stdout, "{}", info.path)?;
    for (name, c) in &info.configs {
    let mut flag = ' ';
    if c.default {
    flag = '*';
    }
    writeln!(stdout, "{} «{}»", flag, name)?;
    write_headers(&mut stdout, c)?;
    }
    }
    if let Some(extra_id) = ids.next() {
    write!(stdout, " (also registered as {}", extra_id)?;
    while let Some(extra_id) = ids.next() {
    write!(stdout, ", {}", extra_id)?;
    }
    writeln!(stdout, ")")?;
  • replacement in pijul/src/commands/pushpull.rs at line 271
    [3.112660][3.112660:112712]()
    Some(SubRemote::Delete { remote }) => {
    [3.112660]
    [3.1177]
    Some(SubRemote::Delete {
    remote: spec,
    exact,
    flood,
    }) => {
  • edit in pijul/src/commands/pushpull.rs at line 278
    [3.346]
    [3.1215]
    let db = aggregate_remote_info(&repo, &mut txn)?;
  • replacement in pijul/src/commands/pushpull.rs at line 281
    [3.1216][3.1216:1356]()
    let Some(ri) = get_remote_by_any(&repo, &mut txn, &remote)? else {
    bail!("No such remote: {}", remote);
    [3.1216]
    [3.1356]
    let label_idx = db.by_label.get(&spec);
    let remote = RemoteId::from_base32(spec.as_bytes());
    let remote_idx = remote.and_then(|r| db.by_id.get(&r));
    let url_idx = db.by_url.get(&spec);
    let Some(&idx) = label_idx.or(remote_idx).or(url_idx) else {
    bail!("No such remote: {}", spec);
  • edit in pijul/src/commands/pushpull.rs at line 289
    [3.1375]
    [3.1375]
    let d = &db.data[idx];
  • replacement in pijul/src/commands/pushpull.rs at line 292
    [3.1376][3.1376:1466]()
    if let Some(id) = ri.id {
    txn.drop_named_remote(id)?;
    [3.1376]
    [3.1466]
    fn remove_named_repo(repo: &mut Repository, name: &str) {
    let idx = repo
    .config
    .remotes
    .iter()
    .position(|c| c.name() == name)
    .unwrap();
    repo.config.remotes.remove(idx);
  • replacement in pijul/src/commands/pushpull.rs at line 302
    [3.1485][3.1485:1625]()
    if repo.config.default_remote.as_deref() == Some(ri.path.as_str()) {
    repo.config.default_remote = None;
    [3.1485]
    [3.112970]
    fn flood_delete(
    exact: bool,
    flood: bool,
    d: &RemoteInfo,
    repo: &mut Repository,
    txn: &mut MutTxn<()>,
    ) -> Result<(), anyhow::Error> {
    let mut delete_count = 0;
    for id in &d.id {
    txn.drop_named_remote(*id)?;
    delete_count += 1;
    }
    if !flood {
    // just in case, but these should never trigger because
    // of the checks beforehand
    assert!(delete_count <= 1);
    }
    if !exact {
    delete_count = 0;
    }
    for (name, _ec) in &d.configs {
    remove_named_repo(repo, name);
    delete_count += 1;
    }
    if !flood {
    assert!(delete_count <= 1);
    }
    if d.default {
    repo.config.default_remote = None;
    }
    Ok(())
    }
    if flood {
    flood_delete(flood, exact, d, &mut repo, &mut txn)?;
    } else {
    // all the flood_delete calls in this block are ensured to only delete at most one thing
    if label_idx.is_some() {
    if !exact && d.id.len() <= 1 && d.configs.len() <= 1 {
    flood_delete(flood, exact, d, &mut repo, &mut txn)?;
    } else {
    let idx = repo
    .config
    .remotes
    .iter()
    .position(|c| c.name() == spec)
    .unwrap();
    repo.config.remotes.remove(idx);
    if d.configs.get(&spec).unwrap().default {
    repo.config.default_remote = None;
    }
    }
    } else if remote_idx.is_some() {
    if !exact && d.id.len() <= 1 && d.configs.len() <= 1 {
    flood_delete(flood, exact, d, &mut repo, &mut txn)?;
    } else {
    txn.drop_named_remote(remote.unwrap())?;
    }
    } else if url_idx.is_some() {
    if exact && !d.configs.is_empty() && !d.id.is_empty() {
    bail!("Cannot delete '{}' since there is both a named remote and an ID associated with it", spec);
    }
    if d.configs.len() > 1 {
    bail!("Cannot delete '{}' since there are multiple named remotes associated with it", spec);
    }
    if d.id.len() > 1 {
    bail!("Cannot delete '{}' since there are multiple IDs associated with it", spec);
    }
    flood_delete(flood, exact, d, &mut repo, &mut txn)?;
    }