67GIAQEUQG3KUD7YTYNUWK33BKWPFVNT4YPQMZ3RCALOZ2STDLRQC EEF5WHUIPNJAD3OQ7XOY4VSDOMRSPMLZ7EWGDRMADGGAY2YYB6OQC H4AU6QRPRDRFW3V7NN5CJ6DHLEUBYGNLRZ5GYV6ULBGRMOPCJQXQC ZSFJT4SFIAS7WBODRZOFKKG4SVYBC5PC6XY75WYN7CCQ3SMV7IUQC SXEYMYF7P4RZMZ46WPL4IZUTSQ2ATBWYZX7QNVMS3SGOYXYOHAGQC 6ZPDI7QGISBFIIJWV4J4JGCZKW2OZA57ZEQSP5YXWRFW3J6PGMVAC L4JXJHWXYNCL4QGJXNKKTOKKTAXKKXBJUUY7HFZGEUZ5A2V5H34QC LGEJSLTYI7Y2CYC3AN6ECMT3D3MTWCAKZPVQEG5MPM2OBW5FQ46AC 3KRGVQFUWFHPOGZOXVTJYNCM4XBRVYITAEOVPKBSAZ5GZIUO5KVQC OWJL5HO72US47LCBHUZVH6ONALVEADWMFAMXK5RMDHLXSCCFSFQAC CCLLB7OIFNFYJZTG3UCI7536TOCWSCSXR67VELSB466R24WLJSDAC A3RM526Y7LUXNYW4TL56YKQ5GVOK2R5D7JJVTSQ6TT5MEXIR6YAAC EUZFFJSOWV4PXDFFPDAFBHFUUMOFEU6ST7JH57YYRRR2SEOXLN6QC ZBNKSYA6PW4DSPC3NCRUZLVHW2GNXMCSDSAGEIKHGHDGGZRBH7ZQC use std::collections::{BTreeSet, HashSet};use std::io::Write;
use std::collections::{BTreeMap, BTreeSet, HashSet};use std::io;use std::io::{Stdout, Write};
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,},
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,
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();
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);
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,},);
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);
// 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;}}}
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());
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)?;
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, ")")?;
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);
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)?;}