Description
Current behavior 😯
After #1093 I tried to integrate this new feature into my gitops tool, but concluded that when depending on Gitoxide as a library dependency, the config interface is a bit confusing. This issue collects my observations so far. Feel free to say it should be split into multiple issues, but I suspect that part of the fix is to change the API, which would needs to be established first. It is possible that the various issues reported here should simply be added to the #467 task list and discussion should continue there?
Section/subsection confusion
It seems that this snipet sets credentials.terminalPrompt
:
repo.snapshot_mut().set_value(&Gitoxide::Credentials::TERMINAL_PROMPT, "false").unwrap();
Not sure how set_subsection_value
is supposed to be used, but it seems unusable for setting values in subsections? All these fail with SubSectionForbidden
. (I suppose the method was introduces for those cases where the subsection is e.g. a branch name or similar?)
repo.snapshot_mut().set_subsection_value(
&Gitoxide::Credentials::TERMINAL_PROMPT,
"credentials".as_bytes().as_bstr(),
"false"
)?;
repo.snapshot_mut().set_subsection_value(
&Gitoxide::Credentials::TERMINAL_PROMPT,
"gitoxide".as_bytes().as_bstr(),
"false"
)?;
repo.snapshot_mut().set_subsection_value(
&Gitoxide::Credentials::TERMINAL_PROMPT,
"gitoxide.credentials".as_bytes().as_bstr(),
"false"
)?;
Snapshot/SnapshotMut confusion
Snapshot boolean accessor is
fn boolean(&self, key: impl Into<&BStr>) -> Option<bool>;
but SnapshotMut boolean accessor is
fn boolean(
&self,
section_name: impl AsRef<str>,
subsection_name: Option<&BStr>,
key: impl AsRef<str>,
) -> Option<Result<bool, value::Error>>;
There seems to be no accessors that take 'static dyn Key
. (Possibly, Key
could be made to implement Into<&Bstr>
.)
Expected behavior 🤔
Expectations on current interface
I would have expected
repo.snapshot_mut().set_value(&Gitoxide::Credentials::TERMINAL_PROMPT, "false").unwrap();
to deactivate terminal prompting by setting gitoxide.credentials.terminalPrompt
. I would also have expected the API to encourage using Key
s as much as possible for type safety.
I would also have expected the set_subsection_value
to be able to set values in ordinary "static" subsections.
Suggested new Interface
As a lib consumer, I think that I would prefer the Snapshot
to expose just:
/// Get the first value referenced by the key, panicing if `key` is unknown.
fn get_value<K>(key: Into<Key<K>>) -> Option<K>;
/// ...
fn get_values<K>(key: Into<Key<K>>) -> Vec<K>;
/// Get the value referenced by the key, returning and KeyNotFoundError if the key is unknown.
fn try_get_value<K>(key: Into<Key<K>>) -> Result<Option<K>, Error>;
/// ...
fn try_get_values<K>(key: Into<Key<K>>) -> Result<Vec<K>, Error>;
Symmetrical methods for "add" and "free" are presumably needed. The
Additionally, some sort of traversal API would be needed, e.g. iter_keys
(or iter_values
or perhaps iter_entries
).
Similarly `SnapshotMut` would have
```rust
/// Set a known configuration value, panicking if the key is unknown. Returns the previous value.
fn set_value<T: ValueType>(key: Into<Key<T>>, value: Into<Value<T>>) -> Option<Value<T>>;
/// Sets a known configuration value, returning the previous value and KeyNotFoundError if the key is unknown.
fn try_set_value<T: ValueType>(key: Into<Key<T>>, value: Into<Value<T>>) -> Result<Option<Value<T>>, Error>;
/// Sets an arbitrary configuration value, possibly creating the necessary config sections automatically. It returns the previous value.
fn set_arbitrary_value<T: ValueType>(key: Into<impl &'b BStr>, value: Into<Value<T>>) -> Option<Value<T>>;
In this marvelous world, there would be a impl Into<Key<K>> for &BStr
and impl Into<Key<K>> for &str
so that you can just pass in data you received from the user without sacrificing type safety. Presumably, the caller would have to construct a key instance for e.g. the branch.<name>.remote
subsection by passing in the name.
I suspect that gix
the command needs at least some of the various methods that exist today, so it might be reasonable to either have a sub-trait for either case, or perhaps two accesseor methods config_snapshot(_mut)
and config_???_snapshot(_mut)
.
Git behavior
When considering a new API, git-config
reports the following actions:
Action
--get get value: name [value-pattern]
--get-all get all values: key [value-pattern]
--get-regexp get values for regexp: name-regex [value-pattern]
--get-urlmatch get value specific for the URL: section[.var] URL
--replace-all replace all matching variables: name value [value-pattern]
--add add a new variable: name value
--unset remove a variable: name [value-pattern]
--unset-all remove all matches: name [value-pattern]
--rename-section rename section: old-name new-name
--remove-section remove a section: name
-l, --list list all
--fixed-value use string equality when comparing values to 'value-pattern'
-e, --edit open an editor
--get-color find the color configured: slot [default]
--get-colorbool find the color setting: slot [stdout-is-tty]
Steps to reproduce 🕹
#[test]
fn set_value() -> crate::Result {
let mut repo = named_repo("make_config_repo.sh")?;
let mut config = repo.config_snapshot_mut();
config.set_value(&Credentials::TERMINAL_PROMPT, "false")?;
config.commit()?;
assert_eq!(repo.config_snapshot().boolean("gitoxide.credentials.terminalPrompt"), Some(false));
// This passes:
// assert_eq!(repo.config_snapshot().boolean("credentials.terminalPrompt"), Some(false));
Ok(())
}
#[test]
fn set_subsection_value() -> crate::Result {
let mut repo = named_repo("make_config_repo.sh")?;
let mut config = repo.config_snapshot_mut();
config.set_subsection_value(&Credentials::TERMINAL_PROMPT, "credentials".as_bytes().as_bstr(), "false")?;
config.commit()?;
// Boom!
assert_eq!(repo.config_snapshot().boolean("gitoxide.credentials.terminalPrompt"), Some(false));
Ok(())
}