Skip to main content

workon/
get_remote_callbacks.rs

1use git2::{Config, ConfigLevel, RemoteCallbacks, Repository};
2use git2_credentials::CredentialHandler;
3
4use crate::error::Result;
5use crate::ssh_config::apply_identity_agent;
6
7/// Build [`git2::RemoteCallbacks`] using the given repo's config to drive
8/// credential resolution. Mirrors `git fetch`/`git push` precedence:
9/// local `.git/config` > worktree config > global > XDG > system.
10pub fn get_remote_callbacks<'a>(
11    repo: &Repository,
12    url: Option<&str>,
13) -> Result<RemoteCallbacks<'a>> {
14    build_callbacks(repo.config()?, url)
15}
16
17/// Build [`git2::RemoteCallbacks`] for operations that run before a repo
18/// exists (e.g. clone). Mirrors `git clone` precedence (global + XDG + system),
19/// tolerating a missing `~/.gitconfig`.
20pub fn get_remote_callbacks_default<'a>(url: Option<&str>) -> Result<RemoteCallbacks<'a>> {
21    build_callbacks(open_default_config_lenient()?, url)
22}
23
24fn build_callbacks<'a>(config: Config, url: Option<&str>) -> Result<RemoteCallbacks<'a>> {
25    if let Some(url) = url {
26        apply_identity_agent(url);
27    }
28    let mut callbacks = RemoteCallbacks::new();
29    let mut credential_handler = CredentialHandler::new(config);
30    callbacks.credentials(move |url, username, allowed| {
31        credential_handler.try_next_credential(url, username, allowed)
32    });
33    Ok(callbacks)
34}
35
36/// Like `Config::open_default()`, but tolerates a missing `~/.gitconfig`.
37///
38/// libgit2's `git_config_open_default` unconditionally stats `$HOME/.gitconfig`
39/// and errors on ENOENT, even when XDG (`~/.config/git/config`) or system
40/// configs exist. Probe each level individually so users without a global config
41/// still pick up credential helpers configured elsewhere.
42fn open_default_config_lenient() -> std::result::Result<Config, git2::Error> {
43    if let Ok(c) = Config::open_default() {
44        return Ok(c);
45    }
46    let mut config = Config::new()?;
47    if let Ok(path) = Config::find_global() {
48        let _ = config.add_file(&path, ConfigLevel::Global, false);
49    }
50    if let Ok(path) = Config::find_xdg() {
51        let _ = config.add_file(&path, ConfigLevel::XDG, false);
52    }
53    if let Ok(path) = Config::find_system() {
54        let _ = config.add_file(&path, ConfigLevel::System, false);
55    }
56    Ok(config)
57}