diff --git a/Cargo.lock b/Cargo.lock index a468f93c..897974a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -407,8 +407,7 @@ checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" [[package]] name = "git2" version = "0.13.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d97249f21e9542caeee9f8e1d150905cd875bf723f5ff771bdb4852eb83a24" +source = "git+https://github.com/rust-lang/git2-rs.git?rev=5fddf7e04dc76e70873569ca9f1de3287ec3edda#5fddf7e04dc76e70873569ca9f1de3287ec3edda" dependencies = [ "bitflags", "libc", @@ -549,8 +548,7 @@ checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701" [[package]] name = "libgit2-sys" version = "0.12.12+1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0100ae90655025134424939f1f60e27e879460d451dff6afedde4f8226cbebfc" +source = "git+https://github.com/rust-lang/git2-rs.git?rev=5fddf7e04dc76e70873569ca9f1de3287ec3edda#5fddf7e04dc76e70873569ca9f1de3287ec3edda" dependencies = [ "cc", "libc", diff --git a/asyncgit/Cargo.toml b/asyncgit/Cargo.toml index 621f07eb..7df7d2c8 100644 --- a/asyncgit/Cargo.toml +++ b/asyncgit/Cargo.toml @@ -13,7 +13,8 @@ keywords = ["git"] [dependencies] scopetime = { path = "../scopetime", version = "0.1" } -git2 = { version = "0.13", features = ["vendored-openssl"] } +# git2 = { version = "0.13", features = ["vendored-openssl"] } +git2 = { git="https://github.com/rust-lang/git2-rs.git", rev="5fddf7e04dc76e70873569ca9f1de3287ec3edda", features = ["vendored-openssl"] } rayon-core = "1.8" crossbeam-channel = "0.4" log = "0.4" diff --git a/asyncgit/src/diff.rs b/asyncgit/src/diff.rs index 7dd02ffd..657bdf21 100644 --- a/asyncgit/src/diff.rs +++ b/asyncgit/src/diff.rs @@ -52,11 +52,11 @@ pub struct AsyncDiff { impl AsyncDiff { /// - pub fn new(sender: Sender) -> Self { + pub fn new(sender: &Sender) -> Self { Self { current: Arc::new(Mutex::new(Request(0, None))), last: Arc::new(Mutex::new(None)), - sender, + sender: sender.clone(), pending: Arc::new(AtomicUsize::new(0)), } } diff --git a/asyncgit/src/lib.rs b/asyncgit/src/lib.rs index 3794fbb7..e5076ad0 100644 --- a/asyncgit/src/lib.rs +++ b/asyncgit/src/lib.rs @@ -11,6 +11,7 @@ pub mod cached; mod commit_files; mod diff; mod error; +mod push; mod revlog; mod status; pub mod sync; @@ -19,6 +20,7 @@ mod tags; pub use crate::{ commit_files::AsyncCommitFiles, diff::{AsyncDiff, DiffParams, DiffType}, + push::{AsyncPush, PushRequest}, revlog::{AsyncLog, FetchStatus}, status::{AsyncStatus, StatusParams}, sync::{ @@ -47,6 +49,8 @@ pub enum AsyncNotification { CommitFiles, /// Tags, + /// + Push, } /// current working director `./` diff --git a/asyncgit/src/push.rs b/asyncgit/src/push.rs new file mode 100644 index 00000000..87dd9f8f --- /dev/null +++ b/asyncgit/src/push.rs @@ -0,0 +1,140 @@ +use crate::{ + error::{Error, Result}, + sync, AsyncNotification, CWD, +}; +use crossbeam_channel::Sender; +use std::sync::{Arc, Mutex}; + +#[derive(Clone, Debug)] +enum PushStates { + None, + // Packing, + // Pushing(usize, usize), +} + +impl Default for PushStates { + fn default() -> Self { + PushStates::None + } +} + +/// +#[derive(Default, Clone, Debug)] +pub struct PushRequest { + /// + pub remote: String, + /// + pub branch: String, +} + +#[derive(Default, Clone, Debug)] +struct PushState { + request: PushRequest, + state: PushStates, +} + +/// +pub struct AsyncPush { + state: Arc>>, + last_result: Arc>>, + sender: Sender, +} + +impl AsyncPush { + /// + pub fn new(sender: &Sender) -> Self { + Self { + state: Arc::new(Mutex::new(None)), + last_result: Arc::new(Mutex::new(None)), + sender: sender.clone(), + } + } + + /// + pub fn is_pending(&self) -> Result { + let state = self.state.lock()?; + Ok(state.is_some()) + } + + /// + pub fn last_result(&self) -> Result> { + let res = self.last_result.lock()?; + Ok(res.clone()) + } + + /// + pub fn request(&mut self, params: PushRequest) -> Result<()> { + log::trace!("request"); + + if self.is_pending()? { + return Ok(()); + } + + self.set_request(¶ms)?; + + let arc_state = Arc::clone(&self.state); + let arc_res = Arc::clone(&self.last_result); + let sender = self.sender.clone(); + + rayon_core::spawn(move || { + //TODO: use channels to communicate progress + let res = sync::push_origin( + CWD, + params.remote.as_str(), + params.branch.as_str(), + ); + + Self::set_result(arc_res, res).expect("result error"); + + Self::clear_request(arc_state).expect("clear error"); + + sender + .send(AsyncNotification::Push) + .expect("error sending push"); + }); + + Ok(()) + } + + fn set_request(&self, params: &PushRequest) -> Result<()> { + let mut state = self.state.lock()?; + + if state.is_some() { + return Err(Error::Generic("pending request".into())); + } + + *state = Some(PushState { + request: params.clone(), + ..PushState::default() + }); + + Ok(()) + } + + fn clear_request( + state: Arc>>, + ) -> Result<()> { + let mut state = state.lock()?; + + *state = None; + + Ok(()) + } + + fn set_result( + arc_result: Arc>>, + res: Result<()>, + ) -> Result<()> { + let mut last_res = arc_result.lock()?; + + *last_res = match res { + Ok(_) => None, + Err(e) => { + log::error!("push error: {}", e); + Some(e.to_string()) + } + }; + + Ok(()) + } +} diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 4eb97b69..21c5041e 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -30,9 +30,7 @@ pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult}; pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; pub use logwalker::LogWalker; -pub use remotes::{ - fetch_origin, get_remotes, push_origin, remote_push_master, -}; +pub use remotes::{fetch_origin, get_remotes, push_origin}; pub use reset::{reset_stage, reset_workdir}; pub use stash::{get_stashes, stash_apply, stash_drop, stash_save}; pub use tags::{get_tags, CommitTags, Tags}; diff --git a/asyncgit/src/sync/remotes.rs b/asyncgit/src/sync/remotes.rs index 916b4f87..6e71e062 100644 --- a/asyncgit/src/sync/remotes.rs +++ b/asyncgit/src/sync/remotes.rs @@ -16,18 +16,6 @@ pub fn get_remotes(repo_path: &str) -> Result> { Ok(remotes) } -/// -pub fn remote_push_master(repo_path: &str) -> Result<()> { - scope_time!("remote_push_master"); - - let repo = utils::repo(repo_path)?; - let mut remote = repo.find_remote("origin")?; - - remote.push(&["refs/heads/master"], None)?; - - Ok(()) -} - /// pub fn fetch_origin(repo_path: &str, branch: &str) -> Result { scope_time!("remote_fetch_master"); @@ -44,14 +32,19 @@ pub fn fetch_origin(repo_path: &str, branch: &str) -> Result { } /// -pub fn push_origin(repo_path: &str, branch: &str) -> Result<()> { +pub fn push_origin( + repo_path: &str, + remote: &str, + branch: &str, +) -> Result<()> { scope_time!("push_origin"); let repo = utils::repo(repo_path)?; - let mut remote = repo.find_remote("origin")?; + let mut remote = repo.find_remote(remote)?; let mut options = PushOptions::new(); options.remote_callbacks(remote_callbacks()); + options.packbuilder_parallelism(0); remote.push(&[branch], Some(&mut options))?; @@ -60,6 +53,17 @@ pub fn push_origin(repo_path: &str, branch: &str) -> Result<()> { fn remote_callbacks<'a>() -> RemoteCallbacks<'a> { let mut callbacks = RemoteCallbacks::new(); + callbacks.push_transfer_progress(|progress, total, bytes| { + log::debug!( + "progress: {}/{} ({} B)", + progress, + total, + bytes, + ); + }); + callbacks.pack_progress(|stage, current, total| { + log::debug!("packing: {:?} - {}/{}", stage, current, total); + }); callbacks.credentials(|url, username_from_url, allowed_types| { log::debug!( "creds: '{}' {:?} ({:?})", diff --git a/src/app.rs b/src/app.rs index adb6f1e4..4136c26d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,8 +5,8 @@ use crate::{ event_pump, CommandBlocking, CommandInfo, CommitComponent, Component, CreateBranchComponent, DrawableComponent, ExternalEditorComponent, HelpComponent, - InspectCommitComponent, MsgComponent, ResetComponent, - StashMsgComponent, TagCommitComponent, + InspectCommitComponent, MsgComponent, PushComponent, + ResetComponent, StashMsgComponent, TagCommitComponent, }, input::{Input, InputEvent, InputState}, keys::{KeyConfig, SharedKeyConfig}, @@ -41,6 +41,7 @@ pub struct App { stashmsg_popup: StashMsgComponent, inspect_commit_popup: InspectCommitComponent, external_editor_popup: ExternalEditorComponent, + push_popup: PushComponent, tag_commit_popup: TagCommitComponent, create_branch_popup: CreateBranchComponent, cmdbar: RefCell, @@ -98,6 +99,12 @@ impl App { theme.clone(), key_config.clone(), ), + push_popup: PushComponent::new( + &queue, + sender, + theme.clone(), + key_config.clone(), + ), tag_commit_popup: TagCommitComponent::new( queue.clone(), theme.clone(), @@ -225,21 +232,7 @@ impl App { flags.insert(new_flags); } - let new_flags = self.process_queue()?; - flags.insert(new_flags); - - if flags.contains(NeedsUpdate::ALL) { - self.update()?; - } - //TODO: make this a queue event? - //NOTE: set when any tree component changed selection - if flags.contains(NeedsUpdate::DIFF) { - self.status_tab.update_diff()?; - self.inspect_commit_popup.update_diff()?; - } - if flags.contains(NeedsUpdate::COMMANDS) { - self.update_commands(); - } + self.process_queue(flags)?; } else if let InputEvent::State(polling_state) = ev { self.external_editor_popup.hide(); if let InputState::Paused = polling_state { @@ -293,10 +286,11 @@ impl App { self.stashing_tab.update_git(ev)?; self.revlog.update_git(ev)?; self.inspect_commit_popup.update_git(ev)?; + self.push_popup.update_git(ev)?; //TODO: better system for this // can we simply process the queue here and everyone just uses the queue to schedule a cmd update? - self.update_commands(); + self.process_queue(NeedsUpdate::COMMANDS)?; Ok(()) } @@ -337,6 +331,7 @@ impl App { stashmsg_popup, inspect_commit_popup, external_editor_popup, + push_popup, tag_commit_popup, create_branch_popup, help, @@ -411,7 +406,28 @@ impl App { self.cmdbar.borrow_mut().set_cmds(self.commands(false)); } - fn process_queue(&mut self) -> Result { + fn process_queue(&mut self, flags: NeedsUpdate) -> Result<()> { + let mut flags = flags; + let new_flags = self.process_internal_events()?; + flags.insert(new_flags); + + if flags.contains(NeedsUpdate::ALL) { + self.update()?; + } + //TODO: make this a queue event? + //NOTE: set when any tree component changed selection + if flags.contains(NeedsUpdate::DIFF) { + self.status_tab.update_diff()?; + self.inspect_commit_popup.update_diff()?; + } + if flags.contains(NeedsUpdate::COMMANDS) { + self.update_commands(); + } + + Ok(()) + } + + fn process_internal_events(&mut self) -> Result { let mut flags = NeedsUpdate::empty(); loop { @@ -458,11 +474,6 @@ impl App { flags .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS); } - InternalEvent::ShowInfoMsg(msg) => { - self.msg.show_info(msg.as_str())?; - flags - .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS); - } InternalEvent::Update(u) => flags.insert(u), InternalEvent::OpenCommit => self.commit.show()?, InternalEvent::PopupStashing(opts) => { @@ -486,6 +497,10 @@ impl App { self.file_to_open = path; flags.insert(NeedsUpdate::COMMANDS) } + InternalEvent::Push(branch) => { + self.push_popup.push(branch)?; + flags.insert(NeedsUpdate::ALL) + } }; Ok(flags) @@ -534,6 +549,7 @@ impl App { res } + //TODO: make this automatic, i keep forgetting to add popups here fn any_popup_visible(&self) -> bool { self.commit.is_visible() || self.help.is_visible() @@ -544,6 +560,7 @@ impl App { || self.external_editor_popup.is_visible() || self.tag_commit_popup.is_visible() || self.create_branch_popup.is_visible() + || self.push_popup.is_visible() } fn draw_popups( @@ -570,6 +587,7 @@ impl App { self.external_editor_popup.draw(f, size)?; self.tag_commit_popup.draw(f, size)?; self.create_branch_popup.draw(f, size)?; + self.push_popup.draw(f, size)?; Ok(()) } diff --git a/src/components/inspect_commit.rs b/src/components/inspect_commit.rs index 21c1708b..2dff7bc5 100644 --- a/src/components/inspect_commit.rs +++ b/src/components/inspect_commit.rs @@ -173,7 +173,7 @@ impl InspectCommitComponent { ), commit_id: None, tags: None, - git_diff: AsyncDiff::new(sender.clone()), + git_diff: AsyncDiff::new(sender), visible: false, key_config, } diff --git a/src/components/mod.rs b/src/components/mod.rs index b137da10..88dfed20 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -10,6 +10,7 @@ mod filetree; mod help; mod inspect_commit; mod msg; +mod push; mod reset; mod stashmsg; mod tag_commit; @@ -31,6 +32,7 @@ pub use filetree::FileTreeComponent; pub use help::HelpComponent; pub use inspect_commit::InspectCommitComponent; pub use msg::MsgComponent; +pub use push::PushComponent; pub use reset::ResetComponent; pub use stashmsg::StashMsgComponent; pub use tag_commit::TagCommitComponent; diff --git a/src/components/msg.rs b/src/components/msg.rs index d3c6e87b..9d3e08c3 100644 --- a/src/components/msg.rs +++ b/src/components/msg.rs @@ -119,13 +119,4 @@ impl MsgComponent { Ok(()) } - - /// - pub fn show_info(&mut self, msg: &str) -> Result<()> { - self.title = strings::msg_title_info(&self.key_config); - self.msg = msg.to_string(); - self.show()?; - - Ok(()) - } } diff --git a/src/components/push.rs b/src/components/push.rs new file mode 100644 index 00000000..91aca8cf --- /dev/null +++ b/src/components/push.rs @@ -0,0 +1,169 @@ +use crate::{ + components::{ + visibility_blocking, CommandBlocking, CommandInfo, Component, + DrawableComponent, + }, + keys::SharedKeyConfig, + queue::{InternalEvent, Queue}, + strings, + ui::{self, style::SharedTheme}, +}; +use anyhow::Result; +use asyncgit::{AsyncNotification, AsyncPush, PushRequest}; +use crossbeam_channel::Sender; +use crossterm::event::Event; +use tui::{ + backend::Backend, + layout::Rect, + widgets::{Block, BorderType, Borders, Clear, Paragraph, Text}, + Frame, +}; + +/// +pub struct PushComponent { + visible: bool, + git_push: AsyncPush, + pending: bool, + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, +} + +impl PushComponent { + /// + pub fn new( + queue: &Queue, + sender: &Sender, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue: queue.clone(), + pending: false, + visible: false, + git_push: AsyncPush::new(sender), + theme, + key_config, + } + } + + /// + pub fn push(&mut self, branch: String) -> Result<()> { + self.pending = true; + self.git_push.request(PushRequest { + remote: String::from("origin"), + branch, + })?; + self.show()?; + Ok(()) + } + + /// + pub fn update_git( + &mut self, + ev: AsyncNotification, + ) -> Result<()> { + if self.is_visible() { + if let AsyncNotification::Push = ev { + self.update()?; + } + } + + Ok(()) + } + + /// + fn update(&mut self) -> Result<()> { + self.pending = self.git_push.is_pending()?; + + if !self.pending { + if let Some(err) = self.git_push.last_result()? { + self.queue.borrow_mut().push_back( + InternalEvent::ShowErrorMsg(format!( + "push failed:\n{}", + err + )), + ); + } + + self.hide(); + } + + Ok(()) + } +} + +impl DrawableComponent for PushComponent { + fn draw( + &self, + f: &mut Frame, + _rect: Rect, + ) -> Result<()> { + if self.visible { + let txt = vec![Text::Raw(strings::PUSH_POPUP_MSG.into())]; + + let area = ui::centered_rect_absolute(25, 3, f.size()); + f.render_widget(Clear, area); + f.render_widget( + Paragraph::new(txt.iter()) + .block( + Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Thick) + .title_style(self.theme.title(true)) + .border_style(self.theme.block(true)), + ) + .style(self.theme.text_danger()), + area, + ); + } + + Ok(()) + } +} + +impl Component for PushComponent { + fn commands( + &self, + out: &mut Vec, + _force_all: bool, + ) -> CommandBlocking { + if self.is_visible() { + out.clear(); + } + + out.push(CommandInfo::new( + strings::commands::close_msg(&self.key_config), + !self.pending, + self.visible, + )); + + visibility_blocking(self) + } + + fn event(&mut self, ev: Event) -> Result { + if self.visible { + if let Event::Key(e) = ev { + if e == self.key_config.enter { + self.hide(); + } + } + return Ok(true); + } + Ok(false) + } + + fn is_visible(&self) -> bool { + self.visible + } + + fn hide(&mut self) { + self.visible = false + } + + fn show(&mut self) -> Result<()> { + self.visible = true; + + Ok(()) + } +} diff --git a/src/queue.rs b/src/queue.rs index 98258489..23af8f40 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -39,8 +39,6 @@ pub enum InternalEvent { /// ShowErrorMsg(String), /// - ShowInfoMsg(String), - /// Update(NeedsUpdate), /// open commit msg input OpenCommit, @@ -56,6 +54,8 @@ pub enum InternalEvent { CreateBranch, /// OpenExternalEditor(Option), + /// + Push(String), } /// diff --git a/src/strings.rs b/src/strings.rs index 4f8da047..42486011 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -4,6 +4,8 @@ pub mod order { pub static NAV: i8 = 1; } +pub static PUSH_POPUP_MSG: &str = "pushing..."; + pub fn title_status(key_config: &SharedKeyConfig) -> String { format!( "Unstaged Changes [{}]", @@ -40,9 +42,6 @@ pub fn msg_opening_editor(_key_config: &SharedKeyConfig) -> String { pub fn msg_title_error(_key_config: &SharedKeyConfig) -> String { "Error".to_string() } -pub fn msg_title_info(_key_config: &SharedKeyConfig) -> String { - "Info".to_string() -} pub fn commit_title(_key_config: &SharedKeyConfig) -> String { "Commit".to_string() } diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 69ba6c79..1ba040ab 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -137,7 +137,7 @@ impl Status { key_config.clone(), false, ), - git_diff: AsyncDiff::new(sender.clone()), + git_diff: AsyncDiff::new(sender), git_status_workdir: AsyncStatus::new(sender.clone()), git_status_stage: AsyncStatus::new(sender.clone()), git_action_executed: false, @@ -327,18 +327,10 @@ impl Status { fn push(&self) { if let Some(branch) = self.index_wd.branch_name() { let branch = format!("refs/heads/{}", branch); - if let Err(e) = sync::push_origin(CWD, branch.as_str()) { - self.queue.borrow_mut().push_back( - InternalEvent::ShowErrorMsg(format!( - "push failed:\n{}", - e - )), - ); - } else { - self.queue.borrow_mut().push_back( - InternalEvent::ShowInfoMsg("pushed".to_string()), - ); - } + + self.queue + .borrow_mut() + .push_back(InternalEvent::Push(branch)); } } }