Skip to content

Commit ab827cf

Browse files
committed
feat: support https push
1 parent b21fad7 commit ab827cf

File tree

8 files changed

+233
-24
lines changed

8 files changed

+233
-24
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

asyncgit/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ rayon-core = "1.8"
1818
crossbeam-channel = "0.5"
1919
log = "0.4"
2020
thiserror = "1.0"
21+
url = "2.1.1"
2122

2223
[dev-dependencies]
2324
tempfile = "3.1"

asyncgit/src/push.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::sync::cred::BasicAuthCredential;
12
use crate::{
23
error::{Error, Result},
34
sync, AsyncNotification, CWD,
@@ -88,6 +89,8 @@ pub struct PushRequest {
8889
pub remote: String,
8990
///
9091
pub branch: String,
92+
///
93+
pub basic_credential: Option<BasicAuthCredential>,
9194
}
9295

9396
#[derive(Default, Clone, Debug)]
@@ -161,6 +164,7 @@ impl AsyncPush {
161164
CWD,
162165
params.remote.as_str(),
163166
params.branch.as_str(),
167+
params.basic_credential,
164168
progress_sender.clone(),
165169
);
166170

asyncgit/src/sync/cred.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//! credentials git helper
2+
3+
use git2::{Config, CredentialHelper};
4+
5+
use crate::error::Result;
6+
use crate::CWD;
7+
8+
/// basic Authentication Credentials
9+
#[derive(Debug, Clone, Default)]
10+
pub struct BasicAuthCredential {
11+
///
12+
pub username: Option<String>,
13+
///
14+
pub password: Option<String>,
15+
}
16+
17+
/// know if username and password are needed for this url
18+
pub fn need_username_password(remote: &str) -> Result<bool> {
19+
let repo = crate::sync::utils::repo(CWD)?;
20+
let url = repo.find_remote(remote)?.url().unwrap().to_owned();
21+
let is_http = url.starts_with("http");
22+
Ok(is_http)
23+
}
24+
25+
/// extract username and password
26+
pub fn extract_username_password(
27+
remote: &str,
28+
) -> Result<BasicAuthCredential> {
29+
let repo = crate::sync::utils::repo(CWD)?;
30+
let url = repo.find_remote(remote)?.url().unwrap().to_owned();
31+
let mut helper = CredentialHelper::new(&url);
32+
33+
if let Ok(config) = Config::open_default() {
34+
helper.config(&config);
35+
}
36+
Ok(match helper.execute() {
37+
Some((username, password)) => BasicAuthCredential {
38+
username: Some(username),
39+
password: Some(password),
40+
},
41+
None => extract_cred_from_url(&url),
42+
})
43+
}
44+
45+
/// extract credentials from url
46+
pub fn extract_cred_from_url(url: &str) -> BasicAuthCredential {
47+
if let Ok(url) = url::Url::parse(url) {
48+
BasicAuthCredential {
49+
username: if url.username() == "" {
50+
None
51+
} else {
52+
Some(url.username().to_owned())
53+
},
54+
password: url.password().map(|pwd| pwd.to_owned()),
55+
}
56+
} else {
57+
BasicAuthCredential {
58+
username: None,
59+
password: None,
60+
}
61+
}
62+
}

asyncgit/src/sync/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod commit;
55
mod commit_details;
66
mod commit_files;
77
mod commits_info;
8+
pub mod cred;
89
pub mod diff;
910
mod hooks;
1011
mod hunks;

asyncgit/src/sync/remotes.rs

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
//!
22
33
use super::CommitId;
4-
use crate::{error::Result, sync::utils};
4+
use crate::{
5+
error::Result, sync::cred::BasicAuthCredential, sync::utils,
6+
};
57
use crossbeam_channel::Sender;
68
use git2::{
79
Cred, Error as GitError, FetchOptions, PackBuilderStage,
810
PushOptions, RemoteCallbacks,
911
};
1012
use scopetime::scope_time;
13+
1114
///
1215
#[derive(Debug, Clone)]
1316
pub enum ProgressNotification {
@@ -69,7 +72,7 @@ pub fn fetch_origin(repo_path: &str, branch: &str) -> Result<usize> {
6972
let mut remote = repo.find_remote("origin")?;
7073

7174
let mut options = FetchOptions::new();
72-
options.remote_callbacks(match remote_callbacks(None) {
75+
options.remote_callbacks(match remote_callbacks(None, None) {
7376
Ok(callback) => callback,
7477
Err(e) => return Err(e),
7578
});
@@ -84,6 +87,7 @@ pub fn push(
8487
repo_path: &str,
8588
remote: &str,
8689
branch: &str,
90+
basic_credential: Option<BasicAuthCredential>,
8791
progress_sender: Sender<ProgressNotification>,
8892
) -> Result<()> {
8993
scope_time!("push_origin");
@@ -94,7 +98,10 @@ pub fn push(
9498
let mut options = PushOptions::new();
9599

96100
options.remote_callbacks(
97-
match remote_callbacks(Some(progress_sender)) {
101+
match remote_callbacks(
102+
Some(progress_sender),
103+
basic_credential,
104+
) {
98105
Ok(callbacks) => callbacks,
99106
Err(e) => return Err(e),
100107
},
@@ -108,6 +115,7 @@ pub fn push(
108115

109116
fn remote_callbacks<'a>(
110117
sender: Option<Sender<ProgressNotification>>,
118+
basic_credential: Option<BasicAuthCredential>,
111119
) -> Result<RemoteCallbacks<'a>> {
112120
let mut callbacks = RemoteCallbacks::new();
113121
let sender_clone = sender.clone();
@@ -165,21 +173,52 @@ fn remote_callbacks<'a>(
165173
})
166174
});
167175
});
168-
callbacks.credentials(|url, username_from_url, allowed_types| {
169-
log::debug!(
170-
"creds: '{}' {:?} ({:?})",
171-
url,
172-
username_from_url,
173-
allowed_types
174-
);
175176

176-
match username_from_url {
177-
Some(username) => Cred::ssh_key_from_agent(username),
178-
None => Err(GitError::from_str(
179-
" Couldn't extract username from url.",
180-
)),
181-
}
182-
});
177+
let mut first_call_to_credentials = true;
178+
callbacks.credentials(
179+
move |url, username_from_url, allowed_types| {
180+
log::debug!(
181+
"creds: '{}' {:?} ({:?})",
182+
url,
183+
username_from_url,
184+
allowed_types
185+
);
186+
if first_call_to_credentials {
187+
first_call_to_credentials = false;
188+
} else {
189+
return Err(GitError::from_str("Bad credentials."));
190+
}
191+
192+
match &basic_credential {
193+
_ if allowed_types.is_ssh_key() => {
194+
match username_from_url {
195+
Some(username) => {
196+
Cred::ssh_key_from_agent(username)
197+
}
198+
None => Err(GitError::from_str(
199+
" Couldn't extract username from url.",
200+
)),
201+
}
202+
}
203+
Some(BasicAuthCredential {
204+
username: Some(user),
205+
password: Some(pwd),
206+
}) if allowed_types.is_user_pass_plaintext() => {
207+
Cred::userpass_plaintext(&user, &pwd)
208+
}
209+
Some(BasicAuthCredential {
210+
username: Some(user),
211+
password: _,
212+
}) if allowed_types.is_username() => {
213+
Cred::username(user)
214+
}
215+
_ if allowed_types.is_default() => Cred::default(),
216+
_ => Err(GitError::from_str(
217+
"Couldn't find credentials",
218+
)),
219+
}
220+
},
221+
);
183222

184223
Ok(callbacks)
185224
}

0 commit comments

Comments
 (0)