Handle named remotes in pijul remote and remote delete
Dependencies
- [2]
EEF5WHUIPrint default remote if set but not registered in the database - [3]
A3RM526YIntegrating identity malleability - [4]
IVLLXQ5ZImproved push/pull reporting - [5]
SXEYMYF7Fixing the bad changes in history (unfortunately, by rebooting). - [6]
X243Z3Y5Recording only the required metadata (can even be changed later!) - [7]
CCLLB7OIUpgrading to Sanakirja 0.15 + version bump - [8]
ZBNKSYA6Fixing a bus error when starting a transaction on a full disk - [9]
EUZFFJSOUpdating Pijul with the latest changes in Libpijul - [10]
YN63NUZOSanakirja 1.0 - [11]
JL4WKA5PImplement the Sanakirja concurrency model in a cross-process way - [12]
3KRGVQFUDo not update the mtime of unmodified files - [13]
L4JXJHWXpijul/*: reorganize imports and remove extern crate - [14]
LGEJSLTYFixing output (including its uses in reset and pull) - [15]
AEPEFS7OWrite help for each argument - [16]
6ZPDI7QGpull uses None as the base case path when outputing repo - [17]
OWJL5HO7Allow deleting remote by URL and remove default remote if set to deleted remote - [18]
VZL5OHF5Remove the obsolete `remote list` command - [19]
QCPIBC6MMake the default remote configurable through the cli - [*]
H4AU6QRPNew config for HTTP remotes - [*]
ZSFJT4SFAllow 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;use std::collections::{BTreeMap, BTreeSet, HashSet};use std::io;use std::io::{Stdout, Write}; - edit in pijul/src/commands/pushpull.rs at line 13
use libpijul::small_string::SmallString; - edit in pijul/src/commands/pushpull.rs at line 15
use pijul_config::{RemoteConfig, RemoteHttpHeader}; - replacement in pijul/src/commands/pushpull.rs at line 38
Delete { remote: String },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
#[derive(Default)] - replacement in pijul/src/commands/pushpull.rs at line 51
id: Option<RemoteId>,path: SmallString,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
fn get_remote_by_any(repo: &Repository,txn: &mut MutTxn<()>,spec: &str,) -> Result<Option<RemoteInfo>, anyhow::Error> {// Try indexing by ID firstif let Some(id) = RemoteId::from_base32(spec.as_bytes()) {if let Some(r) = txn.get_remote(id)? {let path = r.lock().path.clone();/// Aggregates remote information from three sources:/// - the repo database/// - named remotes in the configuration/// - the default remote set in the configurationfn aggregate_remote_info<T>(repo: &Repository, txn: &T) -> Result<RemoteInfos, anyhow::Error>whereT: 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
return Ok(Some(RemoteInfo { id: Some(id), path }));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
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 laterdefault: false,},); - edit in pijul/src/commands/pushpull.rs at line 126
// Look it up by URL - replacement in pijul/src/commands/pushpull.rs at line 128
let path = r.lock().path.clone();let lock = r.lock(); - replacement in pijul/src/commands/pushpull.rs at line 130
if path.as_str() == spec {return Ok(Some(RemoteInfo {id: Some(*r.id()),path,}));}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
// 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),}));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
Ok(None)Ok(RemoteInfos {data,by_id,by_url,by_label,}) - replacement in pijul/src/commands/pushpull.rs at line 186
let mut stdout = std::io::stdout();let mut stdout = io::stdout(); - replacement in pijul/src/commands/pushpull.rs at line 190
let mut printed_default = false;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();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
if Some(lock.path.as_str()) == repo.config.default_remote.as_deref() {printed_default = true;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())?;}// 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
if !printed_default {if let Some(path) = repo.config.default_remote {writeln!(stdout, "* {:26}: {}", "(none)", path)?;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
Some(SubRemote::Delete { remote }) => {Some(SubRemote::Delete {remote: spec,exact,flood,}) => { - edit in pijul/src/commands/pushpull.rs at line 278
let db = aggregate_remote_info(&repo, &mut txn)?; - replacement in pijul/src/commands/pushpull.rs at line 281
let Some(ri) = get_remote_by_any(&repo, &mut txn, &remote)? else {bail!("No such remote: {}", remote);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
let d = &db.data[idx]; - replacement in pijul/src/commands/pushpull.rs at line 292
if let Some(id) = ri.id {txn.drop_named_remote(id)?;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
if repo.config.default_remote.as_deref() == Some(ri.path.as_str()) {repo.config.default_remote = None;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 beforehandassert!(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 thingif 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)?;}