This commit is contained in:
extrawurst 2023-02-19 10:25:53 +00:00
parent d846174c38
commit 3b9f698c29
12 changed files with 196 additions and 249 deletions

View file

@ -4,17 +4,14 @@ use crate::{
asyncjob::{AsyncJob, RunParams},
error::Result,
sync::cred::BasicAuthCredential,
sync::{
remotes::{get_default_remote, tags_missing_remote},
RepoPath,
},
sync::{remotes::tags_missing_remote, RepoPath},
AsyncGitNotification,
};
use std::sync::{Arc, Mutex};
enum JobState {
Request(Option<BasicAuthCredential>),
Request(Option<BasicAuthCredential>, String),
Response(Result<Vec<String>>),
}
@ -30,12 +27,14 @@ impl AsyncRemoteTagsJob {
///
pub fn new(
repo: RepoPath,
remote: String,
basic_credential: Option<BasicAuthCredential>,
) -> Self {
Self {
repo,
state: Arc::new(Mutex::new(Some(JobState::Request(
basic_credential,
remote,
)))),
}
}
@ -45,7 +44,7 @@ impl AsyncRemoteTagsJob {
if let Ok(mut state) = self.state.lock() {
if let Some(state) = state.take() {
return match state {
JobState::Request(_) => None,
JobState::Request(_, _) => None,
JobState::Response(result) => Some(result),
};
}
@ -65,15 +64,12 @@ impl AsyncJob for AsyncRemoteTagsJob {
) -> Result<Self::Notification> {
if let Ok(mut state) = self.state.lock() {
*state = state.take().map(|state| match state {
JobState::Request(basic_credential) => {
let result = get_default_remote(&self.repo)
.and_then(|remote| {
tags_missing_remote(
&self.repo,
&remote,
basic_credential,
)
});
JobState::Request(basic_credential, remote) => {
let result = tags_missing_remote(
&self.repo,
&remote,
basic_credential,
);
JobState::Response(result)
}

View file

@ -5,10 +5,7 @@ pub mod merge_ff;
pub mod merge_rebase;
pub mod rename;
use super::{
remotes::get_default_remote_in_repo, utils::bytes2string,
RepoPath,
};
use super::{utils::bytes2string, RepoPath};
use crate::{
error::{Error, Result},
sync::{repository::repo, utils::get_head_repo, CommitId},
@ -198,6 +195,7 @@ pub struct BranchCompare {
pub(crate) fn branch_set_upstream(
repo: &Repository,
branch_name: &str,
remote: &str,
) -> Result<()> {
scope_time!("branch_set_upstream");
@ -205,7 +203,6 @@ pub(crate) fn branch_set_upstream(
repo.find_branch(branch_name, BranchType::Local)?;
if branch.upstream().is_err() {
let remote = get_default_remote_in_repo(repo)?;
let upstream_name = format!("{remote}/{branch_name}");
branch.set_upstream(Some(upstream_name.as_str()))?;
}

View file

@ -1,8 +1,6 @@
//! credentials git helper
use super::{
remotes::get_default_remote_in_repo, repository::repo, RepoPath,
};
use super::{repository::repo, RepoPath};
use crate::error::{Error, Result};
use git2::CredentialHelper;
@ -30,10 +28,12 @@ impl BasicAuthCredential {
}
/// know if username and password are needed for this url
pub fn need_username_password(repo_path: &RepoPath) -> Result<bool> {
pub fn need_username_password(
repo_path: &RepoPath,
remote: &str,
) -> Result<bool> {
let repo = repo(repo_path)?;
let remote =
repo.find_remote(&get_default_remote_in_repo(&repo)?)?;
let remote = repo.find_remote(remote)?;
let url = remote
.pushurl()
.or_else(|| remote.url())
@ -46,10 +46,11 @@ pub fn need_username_password(repo_path: &RepoPath) -> Result<bool> {
/// extract username and password
pub fn extract_username_password(
repo_path: &RepoPath,
remote: &str,
) -> Result<BasicAuthCredential> {
let repo = repo(repo_path)?;
let url = repo
.find_remote(&get_default_remote_in_repo(&repo)?)?
.find_remote(remote)?
.url()
.ok_or(Error::UnknownRemote)?
.to_owned();
@ -175,7 +176,10 @@ mod tests {
repo.remote(DEFAULT_REMOTE_NAME, "http://user@github.com")
.unwrap();
assert_eq!(need_username_password(repo_path).unwrap(), true);
assert_eq!(
need_username_password(repo_path, "origin").unwrap(),
true
);
}
#[test]
@ -189,7 +193,10 @@ mod tests {
repo.remote(DEFAULT_REMOTE_NAME, "git@github.com:user/repo")
.unwrap();
assert_eq!(need_username_password(repo_path).unwrap(), false);
assert_eq!(
need_username_password(repo_path, "origin").unwrap(),
false
);
}
#[test]
@ -208,7 +215,10 @@ mod tests {
)
.unwrap();
assert_eq!(need_username_password(repo_path).unwrap(), false);
assert_eq!(
need_username_password(repo_path, "origin").unwrap(),
false
);
}
#[test]
@ -221,7 +231,7 @@ mod tests {
let repo_path: &RepoPath =
&root.as_os_str().to_str().unwrap().into();
need_username_password(repo_path).unwrap();
need_username_password(repo_path, "origin").unwrap();
}
#[test]
@ -239,7 +249,7 @@ mod tests {
.unwrap();
assert_eq!(
extract_username_password(repo_path).unwrap(),
extract_username_password(repo_path, "origin").unwrap(),
BasicAuthCredential::new(
Some("user".to_owned()),
Some("pass".to_owned())
@ -259,7 +269,7 @@ mod tests {
.unwrap();
assert_eq!(
extract_username_password(repo_path).unwrap(),
extract_username_password(repo_path, "origin").unwrap(),
BasicAuthCredential::new(Some("user".to_owned()), None)
);
}
@ -274,6 +284,6 @@ mod tests {
let repo_path: &RepoPath =
&root.as_os_str().to_str().unwrap().into();
extract_username_password(repo_path).unwrap();
extract_username_password(repo_path, "origin").unwrap();
}
}

View file

@ -71,7 +71,7 @@ pub use merge::{
};
pub use rebase::rebase_branch;
pub use remotes::{
get_default_remote, get_remotes, has_default_remote,
get_remotes, get_single_remote, has_single_remote,
push::AsyncProgress, tags::PushTagsProgress,
};
pub(crate) use repository::repo;

View file

@ -13,7 +13,7 @@ use crate::{
ProgressPercent,
};
use crossbeam_channel::Sender;
use git2::{BranchType, FetchOptions, ProxyOptions, Repository};
use git2::{BranchType, FetchOptions, ProxyOptions};
use scopetime::scope_time;
use utils::bytes2string;
@ -44,29 +44,11 @@ pub fn get_remotes(repo_path: &RepoPath) -> Result<Vec<String>> {
Ok(remotes)
}
/// tries to find origin or the only remote that is defined if any
/// in case of multiple remotes and none named *origin* we fail
pub fn get_default_remote(repo_path: &RepoPath) -> Result<String> {
/// returns remote name if there is only one single remote
pub fn get_single_remote(repo_path: &RepoPath) -> Result<String> {
let repo = repo(repo_path)?;
get_default_remote_in_repo(&repo)
}
/// see `get_default_remote`
pub(crate) fn get_default_remote_in_repo(
repo: &Repository,
) -> Result<String> {
scope_time!("get_default_remote_in_repo");
let remotes = repo.remotes()?;
// if `origin` exists return that
let found_origin = remotes.iter().any(|r| {
r.map(|r| r == DEFAULT_REMOTE_NAME).unwrap_or_default()
});
if found_origin {
return Ok(DEFAULT_REMOTE_NAME.into());
}
//if only one remote exists pick that
if remotes.len() == 1 {
let first_remote = remotes
@ -85,12 +67,10 @@ pub(crate) fn get_default_remote_in_repo(
Err(Error::NoDefaultRemoteFound)
}
/// returns true based on result of `get_default_remote_in_repo` being anything but `NoDefaultRemoteFound`
pub fn has_default_remote(repo: &Repository) -> bool {
!matches!(
get_default_remote_in_repo(repo),
Err(Error::NoDefaultRemoteFound)
)
/// returns true if there is only a single remote
pub fn has_single_remote(repo_path: &RepoPath) -> Result<bool> {
let repo = repo(repo_path)?;
Ok(repo.remotes()?.len() == 1)
}
///
@ -187,9 +167,7 @@ pub(crate) fn fetch(
#[cfg(test)]
mod tests {
use super::*;
use crate::sync::tests::{
debug_cmd_print, repo_clone, repo_init,
};
use crate::sync::tests::{repo_clone, repo_init};
#[test]
fn test_smoke() {
@ -209,107 +187,4 @@ mod tests {
fetch(repo_path, "master", None, None).unwrap();
}
#[test]
fn test_default_remote() {
let (remote_dir, _remote) = repo_init().unwrap();
let remote_path = remote_dir.path().to_str().unwrap();
let (repo_dir, _repo) = repo_clone(remote_path).unwrap();
let repo_path: &RepoPath = &repo_dir
.into_path()
.as_os_str()
.to_str()
.unwrap()
.into();
debug_cmd_print(
repo_path,
&format!("git remote add second {remote_path}")[..],
);
let remotes = get_remotes(repo_path).unwrap();
assert_eq!(
remotes,
vec![String::from("origin"), String::from("second")]
);
let first =
get_default_remote_in_repo(&repo(repo_path).unwrap())
.unwrap();
assert_eq!(first, String::from("origin"));
}
#[test]
fn test_default_remote_out_of_order() {
let (remote_dir, _remote) = repo_init().unwrap();
let remote_path = remote_dir.path().to_str().unwrap();
let (repo_dir, _repo) = repo_clone(remote_path).unwrap();
let repo_path: &RepoPath = &repo_dir
.into_path()
.as_os_str()
.to_str()
.unwrap()
.into();
debug_cmd_print(
repo_path,
"git remote rename origin alternate",
);
debug_cmd_print(
repo_path,
&format!("git remote add origin {remote_path}")[..],
);
//NOTE: aparently remotes are not chronolically sorted but alphabetically
let remotes = get_remotes(repo_path).unwrap();
assert_eq!(
remotes,
vec![String::from("alternate"), String::from("origin")]
);
let first =
get_default_remote_in_repo(&repo(repo_path).unwrap())
.unwrap();
assert_eq!(first, String::from("origin"));
}
#[test]
fn test_default_remote_inconclusive() {
let (remote_dir, _remote) = repo_init().unwrap();
let remote_path = remote_dir.path().to_str().unwrap();
let (repo_dir, _repo) = repo_clone(remote_path).unwrap();
let repo_path: &RepoPath = &repo_dir
.into_path()
.as_os_str()
.to_str()
.unwrap()
.into();
debug_cmd_print(
repo_path,
"git remote rename origin alternate",
);
debug_cmd_print(
repo_path,
&format!("git remote add someremote {remote_path}")[..],
);
let remotes = get_remotes(repo_path).unwrap();
assert_eq!(
remotes,
vec![
String::from("alternate"),
String::from("someremote")
]
);
let res =
get_default_remote_in_repo(&repo(repo_path).unwrap());
assert_eq!(res.is_err(), true);
assert!(matches!(res, Err(Error::NoDefaultRemoteFound)));
}
}

View file

@ -143,7 +143,8 @@ pub fn push_raw(
scope_time!("push");
let repo = repo(repo_path)?;
let mut remote = repo.find_remote(remote)?;
let remote_name = remote;
let mut remote = repo.find_remote(remote_name)?;
let mut options = PushOptions::new();
options.proxy_options(proxy_auto());
@ -176,7 +177,7 @@ pub fn push_raw(
}
if !delete {
branch_set_upstream(&repo, branch)?;
branch_set_upstream(&repo, branch, remote_name)?;
}
Ok(())

View file

@ -19,7 +19,8 @@ use crate::{
options::{Options, SharedOptions},
popup_stack::PopupStack,
queue::{
Action, InternalEvent, NeedsUpdate, Queue, StackablePopupOpen,
Action, InternalEvent, NeedsUpdate, PushDetails, Queue,
StackablePopupOpen,
},
setup_popups,
strings::{self, ellipsis_trim_start, order},
@ -859,9 +860,8 @@ impl App {
self.file_to_open = path;
flags.insert(NeedsUpdate::COMMANDS);
}
InternalEvent::Push(branch, push_type, force, delete) => {
self.push_popup
.push(branch, push_type, force, delete)?;
InternalEvent::Push(details) => {
self.push_popup.push(details)?;
flags.insert(NeedsUpdate::ALL);
}
InternalEvent::Pull(branch) => {
@ -1005,12 +1005,14 @@ impl App {
))
},
|name| {
InternalEvent::Push(
InternalEvent::Push(PushDetails::new(
name.to_string(),
//TODO:
String::new(),
PushType::Branch,
false,
true,
)
))
},
),
);
@ -1025,32 +1027,39 @@ impl App {
error.to_string(),
));
} else {
let remote = sync::get_default_remote(
&self.repo.borrow(),
)?;
//TODO:
let remote =
sync::get_single_remote(&self.repo.borrow())?;
self.queue.push(InternalEvent::ConfirmAction(
Action::DeleteRemoteTag(tag_name, remote),
Action::DeleteRemoteTag { tag_name, remote },
));
flags.insert(NeedsUpdate::ALL);
self.tags_popup.update_tags()?;
}
}
Action::DeleteRemoteTag(tag_name, _remote) => {
Action::DeleteRemoteTag { tag_name, remote } => {
self.queue.push(InternalEvent::Push(
tag_name,
PushType::Tag,
false,
true,
PushDetails::new(
tag_name,
remote,
PushType::Tag,
false,
true,
),
));
}
Action::ForcePush(branch, force) => {
self.queue.push(InternalEvent::Push(
branch,
PushType::Branch,
force,
false,
PushDetails::new(
branch,
//TODO:
String::new(),
PushType::Branch,
force,
false,
),
));
}
Action::PullMerge { rebase, .. } => {

View file

@ -4,7 +4,7 @@ use crate::{
CommandInfo, Component, DrawableComponent, EventState,
},
keys::{key_match, SharedKeyConfig},
queue::{InternalEvent, Queue},
queue::{InternalEvent, PushDetails, Queue},
strings,
ui::{self, style::SharedTheme},
};
@ -15,10 +15,10 @@ use asyncgit::{
extract_username_password, need_username_password,
BasicAuthCredential,
},
get_branch_remote, get_default_remote, RepoPathRef,
RepoPathRef,
},
AsyncGitNotification, AsyncPush, PushRequest, PushType,
RemoteProgress, RemoteProgressState,
AsyncGitNotification, AsyncPush, PushRequest, RemoteProgress,
RemoteProgressState,
};
use crossbeam_channel::Sender;
use crossterm::event::Event;
@ -52,12 +52,11 @@ impl PushComponentModifier {
pub struct PushComponent {
repo: RepoPathRef,
modifier: PushComponentModifier,
details: Option<PushDetails>,
visible: bool,
git_push: AsyncPush,
progress: Option<RemoteProgress>,
pending: bool,
branch: String,
push_type: PushType,
queue: Queue,
theme: SharedTheme,
key_config: SharedKeyConfig,
@ -79,8 +78,7 @@ impl PushComponent {
modifier: PushComponentModifier::None,
pending: false,
visible: false,
branch: String::new(),
push_type: PushType::Branch,
details: None,
git_push: AsyncPush::new(repo.borrow().clone(), sender),
progress: None,
input_cred: CredComponent::new(
@ -93,16 +91,9 @@ impl PushComponent {
}
///
pub fn push(
&mut self,
branch: String,
push_type: PushType,
force: bool,
delete: bool,
) -> Result<()> {
self.branch = branch;
self.push_type = push_type;
self.modifier = match (force, delete) {
pub fn push(&mut self, details: PushDetails) -> Result<()> {
self.details = Some(details.clone());
self.modifier = match (details.force, details.delete) {
(true, true) => PushComponentModifier::ForceDelete,
(false, true) => PushComponentModifier::Delete,
(true, false) => PushComponentModifier::Force,
@ -113,19 +104,23 @@ impl PushComponent {
self.show()?;
if need_username_password(&self.repo.borrow())? {
let cred = extract_username_password(&self.repo.borrow())
.unwrap_or_else(|_| {
BasicAuthCredential::new(None, None)
});
if need_username_password(
&self.repo.borrow(),
&details.remote,
)? {
let cred = extract_username_password(
&self.repo.borrow(),
&details.remote,
)
.unwrap_or_else(|_| BasicAuthCredential::new(None, None));
if cred.is_complete() {
self.push_to_remote(Some(cred), force)
self.push_to_remote(Some(cred), details.force)
} else {
self.input_cred.set_cred(cred);
self.input_cred.show()
}
} else {
self.push_to_remote(None, force)
self.push_to_remote(None, details.force)
}
}
@ -134,32 +129,35 @@ impl PushComponent {
cred: Option<BasicAuthCredential>,
force: bool,
) -> Result<()> {
let remote = if let Ok(Some(remote)) =
get_branch_remote(&self.repo.borrow(), &self.branch)
{
log::info!("push: branch '{}' has upstream for remote '{}' - using that",self.branch,remote);
remote
} else {
log::info!("push: branch '{}' has no upstream - looking up default remote",self.branch);
let remote = get_default_remote(&self.repo.borrow())?;
log::info!(
"push: branch '{}' to remote '{}'",
self.branch,
remote
);
remote
};
if let Some(details) = &self.details {
// let remote = if let Ok(Some(remote)) = get_branch_remote(
// &self.repo.borrow(),
// &details.branch,
// ) {
// log::info!("push: branch '{}' has upstream for remote '{}' - using that",details.branch,remote);
// remote
// } else {
// log::info!("push: branch '{}' has no upstream - looking up default remote",details.branch);
// let remote = get_default_remote(&self.repo.borrow())?;
// log::info!(
// "push: branch '{}' to remote '{}'",
// details.branch,
// remote
// );
// remote
// };
self.pending = true;
self.progress = None;
self.git_push.request(PushRequest {
remote,
branch: self.branch.clone(),
push_type: self.push_type,
force,
delete: self.modifier.delete(),
basic_credential: cred,
})?;
self.pending = true;
self.progress = None;
self.git_push.request(PushRequest {
remote: details.remote.clone(),
branch: details.branch.clone(),
push_type: details.push_type,
force,
delete: self.modifier.delete(),
basic_credential: cred,
})?;
}
Ok(())
}
@ -333,6 +331,7 @@ impl Component for PushComponent {
fn hide(&mut self) {
self.visible = false;
self.details = None;
}
fn show(&mut self) -> Result<()> {

View file

@ -184,7 +184,7 @@ impl ConfirmComponent {
tag_name,
),
),
Action::DeleteRemoteTag(_tag_name,remote) => (
Action::DeleteRemoteTag{remote,..} => (
strings::confirm_title_delete_tag_remote(),
strings::confirm_msg_delete_tag_remote(remote),
),

View file

@ -22,6 +22,7 @@ struct OptionsData {
pub diff: DiffOptions,
pub status_show_untracked: Option<ShowUntrackedFilesConfig>,
pub commit_msgs: Vec<String>,
pub default_remote: Option<String>,
}
const COMMIT_MSG_HISTRY_LENGTH: usize = 20;
@ -125,6 +126,10 @@ impl Options {
}
}
pub fn default_remote(&self) -> Option<String> {
self.data.default_remote.clone()
}
fn save(&self) {
if let Err(e) = self.save_failable() {
log::error!("options save error: {}", e);

View file

@ -46,7 +46,7 @@ pub enum Action {
DeleteLocalBranch(String),
DeleteRemoteBranch(String),
DeleteTag(String),
DeleteRemoteTag(String, String),
DeleteRemoteTag { tag_name: String, remote: String },
ForcePush(String, bool),
PullMerge { incoming: usize, rebase: bool },
AbortMerge,
@ -68,6 +68,33 @@ pub enum StackablePopupOpen {
CompareCommits(InspectCommitOpen),
}
#[derive(Clone)]
pub struct PushDetails {
pub branch: String,
pub remote: String,
pub push_type: PushType,
pub force: bool,
pub delete: bool,
}
impl PushDetails {
pub fn new(
branch: String,
remote: String,
push_type: PushType,
force: bool,
delete: bool,
) -> Self {
Self {
branch,
remote,
push_type,
force,
delete,
}
}
}
///
pub enum InternalEvent {
///
@ -103,7 +130,7 @@ pub enum InternalEvent {
///
OpenExternalEditor(Option<String>),
///
Push(String, PushType, bool, bool),
Push(PushDetails),
///
Pull(String),
///

View file

@ -8,18 +8,23 @@ use crate::{
},
keys::{key_match, SharedKeyConfig},
options::SharedOptions,
queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem},
queue::{
Action, InternalEvent, NeedsUpdate, PushDetails, Queue,
ResetItem,
},
strings, try_or_popup,
ui::style::SharedTheme,
};
use anyhow::Result;
use anyhow::{Ok, Result};
use asyncgit::{
asyncjob::AsyncSingleJob,
cached,
sync::{
self, status::StatusType, RepoPath, RepoPathRef, RepoState,
},
sync::{BranchCompare, CommitId},
sync::{
get_single_remote, has_single_remote, BranchCompare, CommitId,
},
AsyncBranchesJob, AsyncDiff, AsyncGitNotification, AsyncStatus,
DiffParams, DiffType, PushType, StatusItem, StatusParams,
};
@ -584,10 +589,13 @@ impl Status {
));
} else {
self.queue.push(InternalEvent::Push(
branch,
PushType::Branch,
force,
false,
PushDetails::new(
branch,
String::new(),
PushType::Branch,
force,
false,
),
));
}
}
@ -989,3 +997,23 @@ impl Component for Status {
Ok(())
}
}
pub fn default_remote(
repo: &RepoPath,
options: &SharedOptions,
queue: &Queue,
) -> Result<Option<String>> {
if let Some(default_remote) = options.borrow().default_remote() {
return Ok(Some(default_remote));
}
if has_single_remote(repo)? {
return Ok(Some(get_single_remote(repo)?));
}
queue.push(InternalEvent::ShowErrorMsg(String::from(
"no default remote found, please configure",
)));
Ok(None)
}