add push method

This commit is contained in:
Josh Junon 2024-02-07 13:24:02 +01:00 committed by GitButler
parent 4cf7c384b1
commit 70acaac3af
4 changed files with 152 additions and 0 deletions

View File

@ -566,4 +566,41 @@ impl<E: GitExecutor + 'static> crate::Repository for Repository<E> {
})?
}
}
async fn push(
&self,
remote: &str,
refspec: RefSpec,
authorization: &Authorization,
) -> Result<(), crate::Error<Self::Error>> {
let mut args = vec!["-C", &self.path, "push", "--quiet"];
let refspec_string = refspec.to_string();
args.push(remote);
args.push(&refspec_string);
let (status, stdout, stderr) = self
.execute_with_auth_harness(&args, None, authorization)
.await?;
if status == 0 {
Ok(())
} else {
// Did the ref not match?
if stderr.to_lowercase().contains("does not match any") {
// FIXME(qix-): this fallback doesn't make much sense; might need to be reworked.
Err(crate::Error::RefNotFound(
refspec.source.unwrap_or(refspec_string),
))?
} else {
Err(Error::<E>::Failed {
status,
args: args.into_iter().map(Into::into).collect(),
stdout,
stderr,
})?
}
}
}
}

View File

@ -325,4 +325,78 @@ impl<R: ThreadedResource> crate::Repository for Repository<R> {
.await
.await
}
async fn push(
&self,
remote: &str,
refspec: RefSpec,
authorization: &Authorization,
) -> Result<(), crate::Error<Self::Error>> {
let remote = remote.to_owned();
let authorization = authorization.clone();
self.repo
.with(move |repo| {
let mut remote = repo.find_remote(&remote)?;
let mut callbacks = git2::RemoteCallbacks::new();
callbacks.credentials(|_url, username, _allowed| {
let auth = match &authorization {
Authorization::Auto => {
let cred = git2::Cred::default()?;
Ok(cred)
}
Authorization::Basic { username, password } => {
let username = username.as_deref().unwrap_or_default();
let password = password.as_deref().unwrap_or_default();
git2::Cred::userpass_plaintext(username, password)
}
Authorization::Ssh {
passphrase,
private_key,
} => {
let private_key =
private_key.as_ref().map(PathBuf::from).unwrap_or_else(|| {
let mut path = dirs::home_dir().unwrap();
path.push(".ssh");
path.push("id_rsa");
path
});
let username = username
.map(ToOwned::to_owned)
.unwrap_or_else(|| std::env::var("USER").unwrap_or_default());
git2::Cred::ssh_key(
&username,
None,
&private_key,
passphrase.clone().as_deref(),
)
}
};
auth
});
let mut push_options = git2::PushOptions::new();
push_options.remote_callbacks(callbacks);
let refspec = refspec.to_string();
let r = remote.push(&[&refspec], Some(&mut push_options));
r.map_err(|e| {
if e.code() == git2::ErrorCode::NotFound {
crate::Error::RefNotFound(refspec)
} else {
e.into()
}
})
})
.await
.await
}
}

View File

@ -123,6 +123,36 @@ macro_rules! gitbutler_git_integration_tests {
}
}).await
}
async fn push_with_ssh_basic_no_master(repo, server, server_repo) {
use crate::*;
let auth = Authorization::Basic {
username: Some("my_username".to_owned()),
password: Some("my_password".to_owned()),
};
server.allow_authorization(auth.clone());
server.run_with_server(async move |port| {
repo.create_remote("origin", &format!("[my_username@localhost:{port}]:test.git")).await.unwrap();
let err = repo.push(
"origin",
RefSpec{
source: Some("refs/heads/master".to_owned()),
destination: Some("refs/heads/master".to_owned()),
..Default::default()
},
&auth
).await.unwrap_err();
if let Error::RefNotFound(refname) = err {
assert_eq!(refname, "refs/heads/master");
} else {
panic!("expected RefNotFound, got {:?}", err);
}
}).await
}
}
};
}

View File

@ -115,6 +115,17 @@ pub trait Repository {
/// Gets the URI for a remote.
async fn remote(&self, remote: &str) -> Result<String, Error<Self::Error>>;
/// Pushes the given refspec to the given remote.
///
/// This is an authorized operation; the given authorization
/// credentials will be used to authenticate with the remote.
async fn push(
&self,
remote: &str,
refspec: RefSpec,
authorization: &Authorization,
) -> Result<(), Error<Self::Error>>;
}
/// Provides authentication credentials when performing