Skip to content

Consumer config confusion #1125

Open
Open
@bittrance

Description

@bittrance

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 Keys 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(())
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions