Search code examples
gitauthenticationrustpushlibgit2

"request failed with status code: 401" error when trying to push to remote using git2-rs / libgit2


I have a local git repository that is being maintained through git2-rs, a pretty much one-to-one Rust wrapper around the C library libgit2. I have managed to stage and commit changes using the library. However, I cannot manage to push changes to the remote repository. When I try to connect to the remote I get an error with the following message:

request failed with status code: 401

Here is my code:

let repo: Repository = /* get repository */;
let mut remote = repo.find_remote("origin").unwrap();
// connect returns Err, and so this panics.
remote.connect(Direction::Push).unwrap();

I have also tried to pass various credentials, but the same error occurs:

let mut callbacks = RemoteCallbacks::new();
callbacks.credentials(|str, str_opt, cred_type| {
    Ok(Cred::userpass_plaintext("natanfudge", env!("GITHUB_PASSWORD")).unwrap())
});
remote
    .connect_auth(Direction::Push, Some(callbacks), None)
    .unwrap();
let mut callbacks = RemoteCallbacks::new();
callbacks.credentials(|str, str_opt, cred_type| {
    // This line does not panic, only the connect_auth!
    Ok(Cred::ssh_key_from_agent("natanfudge").expect("Could not get ssh key from ssh agent"))
});
remote
    .connect_auth(Direction::Push, Some(callbacks), None)
    .unwrap();

What am I missing?


Solution

  • Ok, I solved the problem. There are 3 things that need to be done for this to work:

    • Using connect_auth with credentials is right.

    • You also need to specify the same credentials with remote.push.

    • You must specify the same refspec string that you did in remote_add_push in remote.push.

    So this code works:

    fn create_callbacks<'a>() -> RemoteCallbacks<'a>{
        let mut callbacks = RemoteCallbacks::new();
        &callbacks.credentials(|str, str_opt, cred_type| {
            Cred::userpass_plaintext("your-username",env!("GITHUB_PASSWORD"))
        });
        callbacks
    }
    
    fn main() {
        let repo = /* get repository */
    
        let mut remote = repo.find_remote("origin").unwrap();
    
        remote.connect_auth(Direction::Push, Some(create_callbacks()), None).unwrap();
        repo.remote_add_push("origin", "refs/heads/<branch-name>:refs/heads/<branch-name>").unwrap();
        let mut push_options = PushOptions::default();
        let mut callbacks = create_callbacks();
        push_options.remote_callbacks(callbacks);
    
        remote.push(&["refs/heads/<branch-name>:refs/heads/<branch-name>"], Some(&mut push_options)).unwrap();
    
        std::mem::drop(remote);
    
        Ok(())
    }
    

    For debugging, using the push_update_reference callback is useful. It will say if there was a problem pushing.

        let mut push_options = PushOptions::default();
        let mut callbacks = create_callbacks();
        callbacks.push_update_reference(|ref,error|{
           println!("ref = {}, error = {:?}", ref, error);
           Ok(())
        });
    
        remote.push(&["refs/heads/<branch-name>:refs/heads/<branch-name>"], Some(&mut 
        push_options)).unwrap();