diff --git a/CHANGELOG.md b/CHANGELOG.md index 8febc6b3db..4bfaf212c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - added gitui to [chocolatey](https://chocolatey.org/packages/gitui) on windows by [@nils-a](https://github.com/nils-a) - push to remote ([#265](https://github.com/extrawurst/gitui/issues/265)) ([#267](https://github.com/extrawurst/gitui/issues/267)) -![branches](assets/push.gif) +![push](assets/push.gif) +- incoming/outgoing commits to upstream ([#362](https://github.com/extrawurst/gitui/issues/362)) - new branch list popup incl. checkout/delete/rename [[@WizardOhio24](https://github.com/WizardOhio24)] ([#303](https://github.com/extrawurst/gitui/issues/303)) ([#323](https://github.com/extrawurst/gitui/issues/323)) -![push](assets/branches.gif) +![branches](assets/branches.gif) - scrollbar in long commit messages [[@timaliberdov](https://github.com/timaliberdov)] ([#308](https://github.com/extrawurst/gitui/issues/308)) diff --git a/asyncgit/src/sync/branch.rs b/asyncgit/src/sync/branch.rs index d60ab80921..8126385580 100644 --- a/asyncgit/src/sync/branch.rs +++ b/asyncgit/src/sync/branch.rs @@ -2,14 +2,12 @@ use crate::{ error::{Error, Result}, - sync::utils, + sync::{utils, CommitId}, }; use git2::BranchType; use scopetime::scope_time; use utils::get_head_repo; -use super::CommitId; - /// returns the branch-name head is currently pointing to /// this might be expensive, see `cached::BranchName` pub(crate) fn get_branch_name(repo_path: &str) -> Result { @@ -79,6 +77,39 @@ pub fn get_branches_to_display( Ok(branches_for_display) } +/// +#[derive(Debug, Default)] +pub struct BranchCompare { + /// + pub ahead: usize, + /// + pub behind: usize, +} + +/// +pub fn branch_compare_upstream( + repo_path: &str, + branch: &str, +) -> Result { + scope_time!("branch_compare_upstream"); + + let repo = utils::repo(repo_path)?; + + let branch = repo.find_branch(branch, BranchType::Local)?; + let upstream = branch.upstream()?; + + let branch_commit = + branch.into_reference().peel_to_commit()?.id(); + + let upstream_commit = + upstream.into_reference().peel_to_commit()?.id(); + + let (ahead, behind) = + repo.graph_ahead_behind(branch_commit, upstream_commit)?; + + Ok(BranchCompare { ahead, behind }) +} + /// Modify HEAD to point to a branch then checkout head, does not work if there are uncommitted changes pub fn checkout_branch( repo_path: &str, diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 0ace7b0d49..0425381fef 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -20,8 +20,9 @@ pub mod utils; pub(crate) use branch::get_branch_name; pub use branch::{ - checkout_branch, create_branch, delete_branch, - get_branches_to_display, rename_branch, BranchForDisplay, + branch_compare_upstream, checkout_branch, create_branch, + delete_branch, get_branches_to_display, rename_branch, + BranchCompare, BranchForDisplay, }; pub use commit::{amend, commit, tag}; pub use commit_details::{ diff --git a/src/tabs/status.rs b/src/tabs/status.rs index d30a619f73..7d9a10dcdf 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -12,13 +12,17 @@ use crate::{ }; use anyhow::Result; use asyncgit::{ + sync::BranchCompare, sync::{self, status::StatusType}, AsyncDiff, AsyncNotification, AsyncStatus, DiffParams, DiffType, StatusParams, CWD, }; use crossbeam_channel::Sender; use crossterm::event::Event; -use tui::layout::{Constraint, Direction, Layout}; +use tui::{ + layout::{Alignment, Constraint, Direction, Layout}, + widgets::Paragraph, +}; /// #[derive(PartialEq)] @@ -45,6 +49,7 @@ pub struct Status { git_diff: AsyncDiff, git_status_workdir: AsyncStatus, git_status_stage: AsyncStatus, + git_branch_state: BranchCompare, queue: Queue, git_action_executed: bool, key_config: SharedKeyConfig, @@ -95,6 +100,7 @@ impl DrawableComponent for Status { self.index_wd.draw(f, left_chunks[0])?; self.index.draw(f, left_chunks[1])?; self.diff.draw(f, chunks[1])?; + self.draw_branch_state(f, &left_chunks); Ok(()) } @@ -141,10 +147,38 @@ impl Status { git_status_workdir: AsyncStatus::new(sender.clone()), git_status_stage: AsyncStatus::new(sender.clone()), git_action_executed: false, + git_branch_state: BranchCompare::default(), key_config, } } + fn draw_branch_state( + &self, + f: &mut tui::Frame, + chunks: &[tui::layout::Rect], + ) { + let w = Paragraph::new(format!( + "\u{2191}{} \u{2193}{}", + self.git_branch_state.ahead, self.git_branch_state.behind + )) + .alignment(Alignment::Right); + + let mut rect = if self.index_wd.focused() { + let mut rect = chunks[0]; + rect.y += rect.height.saturating_sub(1); + rect + } else { + chunks[1] + }; + + rect.x += 1; + rect.width = rect.width.saturating_sub(2); + rect.height = + rect.height.saturating_sub(rect.height.saturating_sub(1)); + + f.render_widget(w, rect); + } + fn can_focus_diff(&self) -> bool { match self.focus { Focus::WorkDir => self.index_wd.is_file_seleted(), @@ -216,6 +250,7 @@ impl Status { .fetch(StatusParams::new(StatusType::Stage, true))?; self.index_wd.update()?; + self.check_branch_state(); } Ok(()) @@ -356,6 +391,20 @@ impl Status { } } } + + fn check_branch_state(&mut self) { + self.git_branch_state = self.index_wd.branch_name().map_or( + BranchCompare::default(), + |branch| { + sync::branch_compare_upstream(CWD, branch.as_str()) + .unwrap_or_default() + }, + ); + } + + const fn can_push(&self) -> bool { + self.git_branch_state.ahead > 0 + } } impl Component for Status { @@ -381,7 +430,7 @@ impl Component for Status { out.push(CommandInfo::new( strings::commands::status_push(&self.key_config), - self.index_wd.branch_name().is_some(), + self.can_push(), true, )); }