From f5f9437b21a9b41dedd867cee34d3f4198713543 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Sun, 23 Jan 2022 00:11:05 +0100 Subject: [PATCH 1/8] try talking to git credential if store is configured --- asyncgit/src/sync/cred.rs | 58 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/asyncgit/src/sync/cred.rs b/asyncgit/src/sync/cred.rs index 57d375eece..10bc1542fa 100644 --- a/asyncgit/src/sync/cred.rs +++ b/asyncgit/src/sync/cred.rs @@ -1,10 +1,13 @@ //! credentials git helper +use std::process::Command; + use super::{ remotes::get_default_remote_in_repo, repository::repo, RepoPath, }; use crate::error::{Error, Result}; use git2::{Config, CredentialHelper}; +use scopetime::scope_time; /// basic Authentication Credentials #[derive(Debug, Clone, Default, PartialEq)] @@ -55,6 +58,12 @@ pub fn extract_username_password( .to_owned(); let mut helper = CredentialHelper::new(&url); + if use_credential_store(&repo.config()?) { + if let Some(cred) = git_credential_fill(&url) { + return Ok(cred); + } + } + //TODO: look at Cred::credential_helper, //if the username is in the url we need to set it here, //I dont think `config` will pick it up @@ -62,6 +71,7 @@ pub fn extract_username_password( if let Ok(config) = Config::open_default() { helper.config(&config); } + Ok(match helper.execute() { Some((username, password)) => { BasicAuthCredential::new(Some(username), Some(password)) @@ -70,6 +80,54 @@ pub fn extract_username_password( }) } +fn use_credential_store(config: &Config) -> bool { + config + .get_entry("credential.helper") + .ok() + .as_ref() + .and_then(git2::ConfigEntry::value) + .map(|val| val == "store") + .unwrap_or_default() +} + +// tries calling: +// printf "protocol=https\nhost=github.com\n" | git credential fill +// see https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage +// TODO: use input stream +fn git_credential_fill(url: &str) -> Option { + scope_time!("git_credential_fill"); + + let url = url::Url::parse(url).ok()?; + + let host = url.domain()?; + let protocol = url.scheme(); + + let cmd = format!("protocol={}\nhost={}\n", protocol, host); + let cmd = format!("printf \"{}\" | git credential fill", cmd); + + let bash_args = vec!["-c".to_string(), cmd]; + + let res = Command::new("bash").args(bash_args).output().ok()?; + let output = String::from_utf8_lossy(res.stdout.as_slice()); + + let mut res = BasicAuthCredential::default(); + for line in output.lines() { + if let Some(tuple) = line.split_once("=") { + if tuple.0 == "username" { + res.username = Some(tuple.1.to_string()); + } else if tuple.0 == "password" { + res.password = Some(tuple.1.to_string()); + } + } + } + + if res.username.is_some() && res.password.is_some() { + return Some(res); + } + + None +} + /// extract credentials from url pub fn extract_cred_from_url(url: &str) -> BasicAuthCredential { if let Ok(url) = url::Url::parse(url) { From 62af4690307b217a6e4af7f45163a4684e4c2ad2 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Sun, 23 Jan 2022 00:30:21 +0100 Subject: [PATCH 2/8] custom 1.50-compatible split_once --- asyncgit/src/sync/cred.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/asyncgit/src/sync/cred.rs b/asyncgit/src/sync/cred.rs index 10bc1542fa..40623849f1 100644 --- a/asyncgit/src/sync/cred.rs +++ b/asyncgit/src/sync/cred.rs @@ -112,7 +112,7 @@ fn git_credential_fill(url: &str) -> Option { let mut res = BasicAuthCredential::default(); for line in output.lines() { - if let Some(tuple) = line.split_once("=") { + if let Some(tuple) = split_once(line, "=") { if tuple.0 == "username" { res.username = Some(tuple.1.to_string()); } else if tuple.0 == "password" { @@ -128,6 +128,18 @@ fn git_credential_fill(url: &str) -> Option { None } +fn split_once<'a>( + v: &'a str, + splitter: &str, +) -> Option<(&'a str, &'a str)> { + let mut split = v.split(splitter); + + let key = split.next(); + let val = split.next(); + + key.zip(val) +} + /// extract credentials from url pub fn extract_cred_from_url(url: &str) -> BasicAuthCredential { if let Ok(url) = url::Url::parse(url) { From 24cb1843b70c22df14e37423f9568815a5a30ec6 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Sun, 23 Jan 2022 00:32:16 +0100 Subject: [PATCH 3/8] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e387fb6b8..40a5cf058a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - support adding annotations to tags ([#747](https://github.com/extrawurst/gitui/issues/747)) - support inspecting annotation of tag ([#1076](https://github.com/extrawurst/gitui/issues/1076)) - support deleting tag on remote ([#1074](https://github.com/extrawurst/gitui/issues/1074)) +- support git credentials helper ([#800](https://github.com/extrawurst/gitui/issues/800)) ### Fixed - Keep commit message when pre-commit hook fails ([#1035](https://github.com/extrawurst/gitui/issues/1035)) From 1519ef84c5c8c993b577389fb6ade40fdf08a963 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Sun, 23 Jan 2022 00:33:36 +0100 Subject: [PATCH 4/8] update --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40a5cf058a..7cc0fbc0fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - support adding annotations to tags ([#747](https://github.com/extrawurst/gitui/issues/747)) - support inspecting annotation of tag ([#1076](https://github.com/extrawurst/gitui/issues/1076)) - support deleting tag on remote ([#1074](https://github.com/extrawurst/gitui/issues/1074)) -- support git credentials helper ([#800](https://github.com/extrawurst/gitui/issues/800)) +- support git credentials helper (store) ([#800](https://github.com/extrawurst/gitui/issues/800)) ### Fixed - Keep commit message when pre-commit hook fails ([#1035](https://github.com/extrawurst/gitui/issues/1035)) From 08b205d141c419d05991eebecf72c7b3dc167f3f Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Sun, 23 Jan 2022 01:03:44 +0100 Subject: [PATCH 5/8] use get instead of fill --- asyncgit/src/sync/cred.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/asyncgit/src/sync/cred.rs b/asyncgit/src/sync/cred.rs index 40623849f1..d2360a4425 100644 --- a/asyncgit/src/sync/cred.rs +++ b/asyncgit/src/sync/cred.rs @@ -103,11 +103,14 @@ fn git_credential_fill(url: &str) -> Option { let protocol = url.scheme(); let cmd = format!("protocol={}\nhost={}\n", protocol, host); - let cmd = format!("printf \"{}\" | git credential fill", cmd); + let cmd = + format!("printf \"{}\" | git credential-store get", cmd); let bash_args = vec!["-c".to_string(), cmd]; - let res = Command::new("bash").args(bash_args).output().ok()?; + let res = Command::new("bash").args(bash_args).output(); + log::debug!("out: {:?}", res); + let res = res.ok()?; let output = String::from_utf8_lossy(res.stdout.as_slice()); let mut res = BasicAuthCredential::default(); From 2173368aa79ece39e3890c2e3024fdae9b64c30f Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Mon, 24 Jan 2022 00:13:42 +0100 Subject: [PATCH 6/8] use repo config and remove custom cred helper code --- asyncgit/src/sync/cred.rs | 76 ++------------------------------------- 1 file changed, 2 insertions(+), 74 deletions(-) diff --git a/asyncgit/src/sync/cred.rs b/asyncgit/src/sync/cred.rs index d2360a4425..4a46c44494 100644 --- a/asyncgit/src/sync/cred.rs +++ b/asyncgit/src/sync/cred.rs @@ -1,13 +1,10 @@ //! credentials git helper -use std::process::Command; - use super::{ remotes::get_default_remote_in_repo, repository::repo, RepoPath, }; use crate::error::{Error, Result}; -use git2::{Config, CredentialHelper}; -use scopetime::scope_time; +use git2::CredentialHelper; /// basic Authentication Credentials #[derive(Debug, Clone, Default, PartialEq)] @@ -58,17 +55,11 @@ pub fn extract_username_password( .to_owned(); let mut helper = CredentialHelper::new(&url); - if use_credential_store(&repo.config()?) { - if let Some(cred) = git_credential_fill(&url) { - return Ok(cred); - } - } - //TODO: look at Cred::credential_helper, //if the username is in the url we need to set it here, //I dont think `config` will pick it up - if let Ok(config) = Config::open_default() { + if let Ok(config) = repo.config() { helper.config(&config); } @@ -80,69 +71,6 @@ pub fn extract_username_password( }) } -fn use_credential_store(config: &Config) -> bool { - config - .get_entry("credential.helper") - .ok() - .as_ref() - .and_then(git2::ConfigEntry::value) - .map(|val| val == "store") - .unwrap_or_default() -} - -// tries calling: -// printf "protocol=https\nhost=github.com\n" | git credential fill -// see https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage -// TODO: use input stream -fn git_credential_fill(url: &str) -> Option { - scope_time!("git_credential_fill"); - - let url = url::Url::parse(url).ok()?; - - let host = url.domain()?; - let protocol = url.scheme(); - - let cmd = format!("protocol={}\nhost={}\n", protocol, host); - let cmd = - format!("printf \"{}\" | git credential-store get", cmd); - - let bash_args = vec!["-c".to_string(), cmd]; - - let res = Command::new("bash").args(bash_args).output(); - log::debug!("out: {:?}", res); - let res = res.ok()?; - let output = String::from_utf8_lossy(res.stdout.as_slice()); - - let mut res = BasicAuthCredential::default(); - for line in output.lines() { - if let Some(tuple) = split_once(line, "=") { - if tuple.0 == "username" { - res.username = Some(tuple.1.to_string()); - } else if tuple.0 == "password" { - res.password = Some(tuple.1.to_string()); - } - } - } - - if res.username.is_some() && res.password.is_some() { - return Some(res); - } - - None -} - -fn split_once<'a>( - v: &'a str, - splitter: &str, -) -> Option<(&'a str, &'a str)> { - let mut split = v.split(splitter); - - let key = split.next(); - let val = split.next(); - - key.zip(val) -} - /// extract credentials from url pub fn extract_cred_from_url(url: &str) -> BasicAuthCredential { if let Ok(url) = url::Url::parse(url) { From 1861e39816f231c126fe398d5fa77e902d82aa74 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Mon, 24 Jan 2022 00:32:22 +0100 Subject: [PATCH 7/8] reword --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cc0fbc0fa..5c22f0640d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - support adding annotations to tags ([#747](https://github.com/extrawurst/gitui/issues/747)) - support inspecting annotation of tag ([#1076](https://github.com/extrawurst/gitui/issues/1076)) - support deleting tag on remote ([#1074](https://github.com/extrawurst/gitui/issues/1074)) -- support git credentials helper (store) ([#800](https://github.com/extrawurst/gitui/issues/800)) +- support git credentials helper (https) ([#800](https://github.com/extrawurst/gitui/issues/800)) ### Fixed - Keep commit message when pre-commit hook fails ([#1035](https://github.com/extrawurst/gitui/issues/1035)) From 76bea6a0ad51fc84c9d1daa9e2f8fb7e63a75bee Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Mon, 24 Jan 2022 00:36:06 +0100 Subject: [PATCH 8/8] docs --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 751a3cc68e..e83173f62f 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ These are the high level goals before calling out `1.0`: - no support for GPG signing (see [#97](https://github.com/extrawurst/gitui/issues/97)) - no git-lfs support (see [#1089](https://github.com/extrawurst/gitui/discussions/1089)) +- *credential.helper* for https needs to be **explicitly** configured (see [#800](https://github.com/extrawurst/gitui/issues/800) Currently, this tool does not fully substitute the _git shell_, however both tools work well in tandem.