diff --git a/asyncgit/src/remote_tags.rs b/asyncgit/src/remote_tags.rs index 365eb766e5..c48f8789d4 100644 --- a/asyncgit/src/remote_tags.rs +++ b/asyncgit/src/remote_tags.rs @@ -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), + Request(Option, String), Response(Result>), } @@ -30,12 +27,14 @@ impl AsyncRemoteTagsJob { /// pub fn new( repo: RepoPath, + remote: String, basic_credential: Option, ) -> 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 { 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) } diff --git a/asyncgit/src/sync/branch/mod.rs b/asyncgit/src/sync/branch/mod.rs index 89056ecf39..5b7c993ab5 100644 --- a/asyncgit/src/sync/branch/mod.rs +++ b/asyncgit/src/sync/branch/mod.rs @@ -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()))?; } diff --git a/asyncgit/src/sync/cred.rs b/asyncgit/src/sync/cred.rs index 3f908a6352..258dc809aa 100644 --- a/asyncgit/src/sync/cred.rs +++ b/asyncgit/src/sync/cred.rs @@ -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 { +pub fn need_username_password( + repo_path: &RepoPath, + remote: &str, +) -> Result { 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 { /// extract username and password pub fn extract_username_password( repo_path: &RepoPath, + remote: &str, ) -> Result { 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(); } } diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 5bd4b0b799..ad18e726ff 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -71,8 +71,8 @@ pub use merge::{ }; pub use rebase::rebase_branch; pub use remotes::{ - get_default_remote, get_remotes, push::AsyncProgress, - tags::PushTagsProgress, + get_remotes, get_single_remote, has_single_remote, + push::AsyncProgress, tags::PushTagsProgress, }; pub(crate) use repository::repo; pub use repository::{RepoPath, RepoPathRef}; diff --git a/asyncgit/src/sync/remotes/mod.rs b/asyncgit/src/sync/remotes/mod.rs index d17df4c69c..2c4a50e436 100644 --- a/asyncgit/src/sync/remotes/mod.rs +++ b/asyncgit/src/sync/remotes/mod.rs @@ -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> { 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 { +/// returns remote name if there is only one single remote +pub fn get_single_remote(repo_path: &RepoPath) -> Result { 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 { - 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,6 +67,12 @@ pub(crate) fn get_default_remote_in_repo( Err(Error::NoDefaultRemoteFound) } +/// returns true if there is only a single remote +pub fn has_single_remote(repo_path: &RepoPath) -> Result { + let repo = repo(repo_path)?; + Ok(repo.remotes()?.len() == 1) +} + /// fn fetch_from_remote( repo_path: &RepoPath, @@ -179,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() { @@ -201,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))); - } } diff --git a/asyncgit/src/sync/remotes/push.rs b/asyncgit/src/sync/remotes/push.rs index 37cdd4a9ca..e65b5e1e4e 100644 --- a/asyncgit/src/sync/remotes/push.rs +++ b/asyncgit/src/sync/remotes/push.rs @@ -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(()) diff --git a/src/app.rs b/src/app.rs index cb90c51509..90b84d4d48 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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, .. } => { diff --git a/src/components/push.rs b/src/components/push.rs index a8295a7e3f..4375abfdd1 100644 --- a/src/components/push.rs +++ b/src/components/push.rs @@ -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, visible: bool, git_push: AsyncPush, progress: Option, 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,37 +91,36 @@ 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, (false, false) => PushComponentModifier::None, }; + //TODO: check if branch is tracked on a remote or if we find a default remote if not go into a remote selection popup + 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) } } @@ -132,32 +129,35 @@ impl PushComponent { cred: Option, 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(()) } @@ -331,6 +331,7 @@ impl Component for PushComponent { fn hide(&mut self) { self.visible = false; + self.details = None; } fn show(&mut self) -> Result<()> { diff --git a/src/components/reset.rs b/src/components/reset.rs index c5e9f0cde3..74d771b6fe 100644 --- a/src/components/reset.rs +++ b/src/components/reset.rs @@ -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), ), diff --git a/src/components/taglist.rs b/src/components/taglist.rs index 2020d8a9fa..8232e1cd10 100644 --- a/src/components/taglist.rs +++ b/src/components/taglist.rs @@ -384,7 +384,7 @@ impl TagListComponent { Ok(()) } - pub fn update_missing_remote_tags(&mut self) { + fn update_missing_remote_tags(&mut self, remote: String) { if self.has_remotes { self.async_remote_tags.spawn(AsyncRemoteTagsJob::new( self.repo.borrow().clone(), diff --git a/src/options.rs b/src/options.rs index 4c0424c710..7b2e9eaf27 100644 --- a/src/options.rs +++ b/src/options.rs @@ -22,6 +22,7 @@ struct OptionsData { pub diff: DiffOptions, pub status_show_untracked: Option, pub commit_msgs: Vec, + pub default_remote: Option, } const COMMIT_MSG_HISTRY_LENGTH: usize = 20; @@ -125,6 +126,10 @@ impl Options { } } + pub fn default_remote(&self) -> Option { + self.data.default_remote.clone() + } + fn save(&self) { if let Err(e) = self.save_failable() { log::error!("options save error: {}", e); diff --git a/src/queue.rs b/src/queue.rs index 81282b29c2..def0e82df7 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -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), /// - Push(String, PushType, bool, bool), + Push(PushDetails), /// Pull(String), /// diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 331fe68dc0..515536c949 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -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,14 @@ impl Status { )); } else { self.queue.push(InternalEvent::Push( - branch, - PushType::Branch, - force, - false, + PushDetails::new( + branch, + //TODO: + String::new(), + PushType::Branch, + force, + false, + ), )); } } @@ -989,3 +998,23 @@ impl Component for Status { Ok(()) } } + +pub fn default_remote( + repo: &RepoPath, + options: &SharedOptions, + queue: &Queue, +) -> Result> { + 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) +}

AltStyle によって変換されたページ (->オリジナル) /