diff --git a/asyncgit/src/lib.rs b/asyncgit/src/lib.rs index c9a1a121b41..7d2cf9b3ae7 100644 --- a/asyncgit/src/lib.rs +++ b/asyncgit/src/lib.rs @@ -33,6 +33,8 @@ mod progress; mod push; mod push_tags; pub mod remote_progress; +/// +pub mod remotes; mod revlog; mod status; pub mod sync; @@ -85,6 +87,9 @@ pub enum AsyncGitNotification { /// //TODO: this does not belong here SyntaxHighlighting, + /// + //TODO: this does not belong here + RemoteTags, } /// current working directory `./` diff --git a/asyncgit/src/remotes.rs b/asyncgit/src/remotes.rs new file mode 100644 index 00000000000..7302e9194bb --- /dev/null +++ b/asyncgit/src/remotes.rs @@ -0,0 +1,72 @@ +use crate::{ + asyncjob::AsyncJob, + error::Result, + sync::cred::BasicAuthCredential, + sync::remotes::{get_default_remote, tags_missing_remote}, + CWD, +}; + +use std::sync::{Arc, Mutex}; + +enum JobState { + Request(Option), + Response(Result>), +} + +/// +#[derive(Clone, Default)] +pub struct AsyncRemoteTagsJob { + state: Arc>>, +} + +/// +impl AsyncRemoteTagsJob { + /// + pub fn new( + basic_credential: Option, + ) -> Self { + Self { + state: Arc::new(Mutex::new(Some(JobState::Request( + basic_credential, + )))), + } + } + + /// + pub fn result(&self) -> Option>> { + if let Ok(mut state) = self.state.lock() { + if let Some(state) = state.take() { + return match state { + JobState::Request(_) => None, + JobState::Response(result) => Some(result), + }; + } + } + + None + } +} + +impl AsyncJob for AsyncRemoteTagsJob { + fn run(&mut self) { + if let Ok(mut state) = self.state.lock() { + *state = state.take().map(|state| match state { + JobState::Request(basic_credential) => { + let result = + get_default_remote(CWD).and_then(|remote| { + tags_missing_remote( + CWD, + &remote, + basic_credential, + ) + }); + + JobState::Response(result) + } + JobState::Response(result) => { + JobState::Response(result) + } + }); + } + } +} diff --git a/asyncgit/src/sync/remotes/mod.rs b/asyncgit/src/sync/remotes/mod.rs index 20f55209a5d..d82905c28d2 100644 --- a/asyncgit/src/sync/remotes/mod.rs +++ b/asyncgit/src/sync/remotes/mod.rs @@ -16,6 +16,8 @@ use push::remote_callbacks; use scopetime::scope_time; use utils::bytes2string; +pub use tags::tags_missing_remote; + /// origin pub const DEFAULT_REMOTE_NAME: &str = "origin"; diff --git a/asyncgit/src/sync/remotes/tags.rs b/asyncgit/src/sync/remotes/tags.rs index 52deb1e7296..de819ed22b6 100644 --- a/asyncgit/src/sync/remotes/tags.rs +++ b/asyncgit/src/sync/remotes/tags.rs @@ -73,7 +73,7 @@ fn remote_tag_refs( } /// lists the remotes tags missing -fn tags_missing_remote( +pub fn tags_missing_remote( repo_path: &str, remote: &str, basic_credential: Option, diff --git a/src/app.rs b/src/app.rs index 74e6b37c024..38fef8074f5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -167,6 +167,7 @@ impl App { ), tags_popup: TagListComponent::new( &queue, + sender, theme.clone(), key_config.clone(), ), @@ -357,6 +358,7 @@ impl App { self.push_tags_popup.update_git(ev)?; self.pull_popup.update_git(ev)?; self.revision_files_popup.update(ev); + self.tags_popup.update(ev); //TODO: better system for this // can we simply process the queue here and everyone just uses the queue to schedule a cmd update? @@ -383,6 +385,7 @@ impl App { || self.push_tags_popup.any_work_pending() || self.pull_popup.any_work_pending() || self.revision_files_popup.any_work_pending() + || self.tags_popup.any_work_pending() } /// diff --git a/src/components/taglist.rs b/src/components/taglist.rs index d8910287852..c110622d0d4 100644 --- a/src/components/taglist.rs +++ b/src/components/taglist.rs @@ -11,9 +11,13 @@ use crate::{ }; use anyhow::Result; use asyncgit::{ + asyncjob::AsyncSingleJob, + remotes::AsyncRemoteTagsJob, + sync::cred::{extract_username_password, need_username_password}, sync::{get_tags_with_metadata, TagWithMetadata}, - CWD, + AsyncGitNotification, CWD, }; +use crossbeam_channel::Sender; use crossterm::event::Event; use std::convert::TryInto; use tui::{ @@ -36,6 +40,9 @@ pub struct TagListComponent { visible: bool, table_state: std::cell::Cell, current_height: std::cell::Cell, + missing_remote_tags: Option>, + async_remote_tags: + AsyncSingleJob, key_config: SharedKeyConfig, } @@ -65,6 +72,8 @@ impl DrawableComponent for TagListComponent { }); let constraints = [ + // symbol if tag is not yet on remote and can be pushed + Constraint::Length(1), // tag name Constraint::Length(tag_name_width.try_into()?), // commit date @@ -230,6 +239,7 @@ impl Component for TagListComponent { impl TagListComponent { pub fn new( queue: &Queue, + sender: &Sender, theme: SharedTheme, key_config: SharedKeyConfig, ) -> Self { @@ -240,6 +250,11 @@ impl TagListComponent { visible: false, table_state: std::cell::Cell::new(TableState::default()), current_height: std::cell::Cell::new(0), + missing_remote_tags: None, + async_remote_tags: AsyncSingleJob::new( + sender.clone(), + AsyncGitNotification::RemoteTags, + ), key_config, } } @@ -251,9 +266,41 @@ impl TagListComponent { self.update_tags()?; + let basic_credential = if need_username_password()? { + let credential = extract_username_password()?; + + if credential.is_complete() { + Some(credential) + } else { + None + } + } else { + None + }; + + self.async_remote_tags + .spawn(AsyncRemoteTagsJob::new(basic_credential)); + Ok(()) } + /// + pub fn update(&mut self, event: AsyncGitNotification) { + if event == AsyncGitNotification::RemoteTags { + if let Some(job) = self.async_remote_tags.take_last() { + if let Some(Ok(missing_remote_tags)) = job.result() { + self.missing_remote_tags = + Some(missing_remote_tags); + } + } + } + } + + /// + pub fn any_work_pending(&self) -> bool { + self.async_remote_tags.is_pending() + } + /// fetch list of tags pub fn update_tags(&mut self) -> Result<()> { let tags = get_tags_with_metadata(CWD)?; @@ -307,7 +354,27 @@ impl TagListComponent { /// fn get_row(&self, tag: &TagWithMetadata) -> Row { + const UPSTREAM_SYMBOL: &str = "\u{2191}"; + const EMPTY_SYMBOL: &str = " "; + + let is_tag_missing_on_remote = self + .missing_remote_tags + .as_ref() + .map_or(false, |missing_remote_tags| { + let remote_tag = format!("refs/tags/{}", tag.name); + + missing_remote_tags.contains(&remote_tag) + }); + + let has_remote_str = if is_tag_missing_on_remote { + UPSTREAM_SYMBOL + } else { + EMPTY_SYMBOL + }; + let cells: Vec = vec![ + Cell::from(has_remote_str) + .style(self.theme.commit_author(false)), Cell::from(tag.name.clone()) .style(self.theme.text(true, false)), Cell::from(utils::time_to_string(tag.time, true))