| use std::convert::TryInto; |
| |
| use crate::{ |
| bstr::{BStr, BString}, |
| config, remote, Remote, |
| }; |
| |
| /// The error returned by [`Remote::save_to()`]. |
| #[derive(Debug, thiserror::Error)] |
| #[allow(missing_docs)] |
| pub enum Error { |
| #[error("The remote pointing to {} is anonymous and can't be saved.", url.to_bstring())] |
| NameMissing { url: gix_url::Url }, |
| } |
| |
| /// The error returned by [`Remote::save_as_to()`]. |
| /// |
| /// Note that this type should rather be in the `as` module, but cannot be as it's part of the Rust syntax. |
| #[derive(Debug, thiserror::Error)] |
| #[allow(missing_docs)] |
| pub enum AsError { |
| #[error(transparent)] |
| Save(#[from] Error), |
| #[error(transparent)] |
| Name(#[from] crate::remote::name::Error), |
| } |
| |
| /// Serialize into git-config. |
| impl Remote<'_> { |
| /// Save ourselves to the given `config` if we are a named remote or fail otherwise. |
| /// |
| /// Note that all sections named `remote "<name>"` will be cleared of all values we are about to write, |
| /// and the last `remote "<name>"` section will be containing all relevant values so that reloading the remote |
| /// from `config` would yield the same in-memory state. |
| #[allow(clippy::result_large_err)] |
| pub fn save_to(&self, config: &mut gix_config::File<'static>) -> Result<(), Error> { |
| fn as_key(name: &str) -> gix_config::parse::section::Key<'_> { |
| name.try_into().expect("valid") |
| } |
| let name = self.name().ok_or_else(|| Error::NameMissing { |
| url: self |
| .url |
| .as_ref() |
| .or(self.push_url.as_ref()) |
| .expect("one url is always set") |
| .to_owned(), |
| })?; |
| if let Some(section_ids) = config.sections_and_ids_by_name("remote").map(|it| { |
| it.filter_map(|(s, id)| (s.header().subsection_name() == Some(name.as_bstr())).then_some(id)) |
| .collect::<Vec<_>>() |
| }) { |
| let mut sections_to_remove = Vec::new(); |
| const KEYS_TO_REMOVE: &[&str] = &[ |
| config::tree::Remote::URL.name, |
| config::tree::Remote::PUSH_URL.name, |
| config::tree::Remote::FETCH.name, |
| config::tree::Remote::PUSH.name, |
| config::tree::Remote::TAG_OPT.name, |
| ]; |
| for id in section_ids { |
| let mut section = config.section_mut_by_id(id).expect("just queried"); |
| let was_empty = section.num_values() == 0; |
| |
| for key in KEYS_TO_REMOVE { |
| while section.remove(key).is_some() {} |
| } |
| |
| let is_empty_after_deletions_of_values_to_be_written = section.num_values() == 0; |
| if !was_empty && is_empty_after_deletions_of_values_to_be_written { |
| sections_to_remove.push(id); |
| } |
| } |
| for id in sections_to_remove { |
| config.remove_section_by_id(id); |
| } |
| } |
| let mut section = config |
| .section_mut_or_create_new("remote", Some(name.as_ref())) |
| .expect("section name is validated and 'remote' is acceptable"); |
| if let Some(url) = self.url.as_ref() { |
| section.push(as_key("url"), Some(url.to_bstring().as_ref())); |
| } |
| if let Some(url) = self.push_url.as_ref() { |
| section.push(as_key("pushurl"), Some(url.to_bstring().as_ref())); |
| } |
| if self.fetch_tags != Default::default() { |
| section.push( |
| as_key(config::tree::Remote::TAG_OPT.name), |
| BStr::new(match self.fetch_tags { |
| remote::fetch::Tags::All => "--tags", |
| remote::fetch::Tags::None => "--no-tags", |
| remote::fetch::Tags::Included => unreachable!("BUG: the default shouldn't be written and we try"), |
| }) |
| .into(), |
| ); |
| } |
| for (key, spec) in self |
| .fetch_specs |
| .iter() |
| .map(|spec| ("fetch", spec)) |
| .chain(self.push_specs.iter().map(|spec| ("push", spec))) |
| { |
| section.push(as_key(key), Some(spec.to_ref().to_bstring().as_ref())); |
| } |
| Ok(()) |
| } |
| |
| /// Forcefully set our name to `name` and write our state to `config` similar to [`save_to()`][Self::save_to()]. |
| /// |
| /// Note that this sets a name for anonymous remotes, but overwrites the name for those who were named before. |
| /// If this name is different from the current one, the git configuration will still contain the previous name, |
| /// and the caller should account for that. |
| #[allow(clippy::result_large_err)] |
| pub fn save_as_to( |
| &mut self, |
| name: impl Into<BString>, |
| config: &mut gix_config::File<'static>, |
| ) -> Result<(), AsError> { |
| let name = crate::remote::name::validated(name)?; |
| let prev_name = self.name.take(); |
| self.name = Some(name.into()); |
| self.save_to(config).map_err(|err| { |
| self.name = prev_name; |
| err.into() |
| }) |
| } |
| } |