Skip to main content

workon/
get_remote_callbacks.rs

1use auth_git2::GitAuthenticator;
2use git2::{Config, ConfigLevel, RemoteCallbacks, Repository};
3
4use crate::error::Result;
5use crate::ssh_config::apply_identity_agent;
6
7/// Holds the credential authenticator and git config needed to build
8/// [`git2::RemoteCallbacks`].
9///
10/// auth-git2's credential closure borrows both the authenticator and the config,
11/// so this struct keeps them alive while the callbacks are in use. Call
12/// [`RemoteAuth::callbacks`] to obtain borrowing callbacks tied to this holder's
13/// lifetime.
14pub struct RemoteAuth {
15    authenticator: GitAuthenticator,
16    config: Config,
17}
18
19impl RemoteAuth {
20    /// Build credential callbacks that borrow this holder.
21    pub fn callbacks(&self) -> RemoteCallbacks<'_> {
22        let mut callbacks = RemoteCallbacks::new();
23        callbacks.credentials(self.authenticator.credentials(&self.config));
24        callbacks
25    }
26}
27
28/// Build a [`RemoteAuth`] using the given repo's config to drive credential
29/// resolution. Mirrors `git fetch`/`git push` precedence:
30/// local `.git/config` > worktree config > global > XDG > system.
31pub fn get_remote_callbacks(repo: &Repository, url: Option<&str>) -> Result<RemoteAuth> {
32    build_auth(repo.config()?, url)
33}
34
35/// Build a [`RemoteAuth`] for operations that run before a repo exists (e.g.
36/// clone). Mirrors `git clone` precedence (global + XDG + system), tolerating
37/// a missing `~/.gitconfig`.
38pub fn get_remote_callbacks_default(url: Option<&str>) -> Result<RemoteAuth> {
39    build_auth(open_default_config_lenient()?, url)
40}
41
42fn build_auth(config: Config, url: Option<&str>) -> Result<RemoteAuth> {
43    if let Some(url) = url {
44        apply_identity_agent(url);
45    }
46    Ok(RemoteAuth {
47        authenticator: GitAuthenticator::default(),
48        config,
49    })
50}
51
52/// Like `Config::open_default()`, but tolerates a missing `~/.gitconfig`.
53///
54/// libgit2's `git_config_open_default` unconditionally stats `$HOME/.gitconfig`
55/// and errors on ENOENT, even when XDG (`~/.config/git/config`) or system
56/// configs exist. Probe each level individually so users without a global config
57/// still pick up credential helpers configured elsewhere.
58fn open_default_config_lenient() -> std::result::Result<Config, git2::Error> {
59    if let Ok(c) = Config::open_default() {
60        return Ok(c);
61    }
62    let mut config = Config::new()?;
63    if let Ok(path) = Config::find_global() {
64        let _ = config.add_file(&path, ConfigLevel::Global, false);
65    }
66    if let Ok(path) = Config::find_xdg() {
67        let _ = config.add_file(&path, ConfigLevel::XDG, false);
68    }
69    if let Ok(path) = Config::find_system() {
70        let _ = config.add_file(&path, ConfigLevel::System, false);
71    }
72    Ok(config)
73}