From 8624ec54a58027a41ba957c7580603de89fa32e0 Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Mon, 20 Jan 2025 11:03:22 +0800 Subject: [PATCH 01/14] chore: add .editorconfig file --- .editorconfig | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..3e3f23ae4a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[*.rs] +indent_style = tab From aaa1bfa19f39c5211952f47baad528f85bc6da50 Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Mon, 20 Jan 2025 11:04:52 +0800 Subject: [PATCH 02/14] feat: add discard_status function --- asyncgit/src/sync/status.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/asyncgit/src/sync/status.rs b/asyncgit/src/sync/status.rs index 1cd5bcc847..1b33129545 100644 --- a/asyncgit/src/sync/status.rs +++ b/asyncgit/src/sync/status.rs @@ -195,3 +195,23 @@ pub fn get_status( Ok(res) } + +/// discard all changes in the working directory +pub fn discard_status(repo_path: &RepoPath) -> Result { + let repo = repo(repo_path)?; + let statuses = repo.statuses(None)?; + + for status in statuses.iter() { + if status.status().is_wt_modified() + || status.status().is_wt_new() + { + let oid = repo.head()?.target().unwrap(); + let obj = repo.find_object(oid, None)?; + + repo.checkout_tree(&obj, None)?; + repo.reset(&obj, git2::ResetType::Hard, None)?; + } + } + + Ok(true) +} From 160f9f866a1f27fd8d75840ee6cd2227d991278f Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Mon, 20 Jan 2025 11:08:24 +0800 Subject: [PATCH 03/14] feat: support choosing checkout branch method Allow choosing a checkout branch method when status is not empty --- src/app.rs | 11 +- src/popups/branchlist.rs | 43 +++-- src/popups/checkout_option.rs | 307 ++++++++++++++++++++++++++++++++++ src/popups/mod.rs | 2 + src/queue.rs | 5 +- 5 files changed, 351 insertions(+), 17 deletions(-) create mode 100644 src/popups/checkout_option.rs diff --git a/src/app.rs b/src/app.rs index 45037f048f..ff7fc97fe2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -19,7 +19,7 @@ use crate::{ RemoteListPopup, RenameBranchPopup, RenameRemotePopup, ResetPopup, RevisionFilesPopup, StashMsgPopup, SubmodulesListPopup, TagCommitPopup, TagListPopup, - UpdateRemoteUrlPopup, + UpdateRemoteUrlPopup, CheckoutOptionPopup }, queue::{ Action, AppTabs, InternalEvent, NeedsUpdate, Queue, @@ -98,6 +98,7 @@ pub struct App { submodule_popup: SubmodulesListPopup, tags_popup: TagListPopup, reset_popup: ResetPopup, + checkout_option_popup: CheckoutOptionPopup, cmdbar: RefCell, tab: usize, revlog: Revlog, @@ -218,6 +219,7 @@ impl App { stashing_tab: Stashing::new(&env), stashlist_tab: StashList::new(&env), files_tab: FilesTab::new(&env), + checkout_option_popup: CheckoutOptionPopup::new(&env), tab: 0, queue: env.queue, theme: env.theme, @@ -493,6 +495,7 @@ impl App { fetch_popup, tag_commit_popup, reset_popup, + checkout_option_popup, create_branch_popup, create_remote_popup, rename_remote_popup, @@ -533,6 +536,7 @@ impl App { submodule_popup, tags_popup, reset_popup, + checkout_option_popup, create_branch_popup, rename_branch_popup, revision_files_popup, @@ -904,7 +908,10 @@ impl App { } InternalEvent::CommitSearch(options) => { self.revlog.search(options); - } + }, + InternalEvent::CheckoutOption(branch, is_local) => { + self.checkout_option_popup.open(branch, is_local)?; + } }; Ok(flags) diff --git a/src/popups/branchlist.rs b/src/popups/branchlist.rs index 9eb5c57e09..4d6a8d6c4b 100644 --- a/src/popups/branchlist.rs +++ b/src/popups/branchlist.rs @@ -13,6 +13,7 @@ use crate::{ ui::{self, Size}, }; use anyhow::Result; +use asyncgit::sync::status::StatusType; use asyncgit::{ sync::{ self, @@ -582,23 +583,37 @@ impl BranchListPopup { anyhow::bail!("no valid branch selected"); } - if self.local { - checkout_branch( - &self.repo.borrow(), - &self.branches[self.selection as usize].name, - )?; - self.hide(); + let status = sync::status::get_status( + &self.repo.borrow(), + StatusType::WorkingDir, + None, + ) + .unwrap(); + + let selected_branch = &self.branches[self.selection as usize]; + if status.is_empty() { + if self.local { + checkout_branch( + &self.repo.borrow(), + &selected_branch.name, + )?; + self.hide(); + } else { + checkout_remote_branch( + &self.repo.borrow(), + &selected_branch, + )?; + self.local = true; + self.update_branches()?; + } + self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); } else { - checkout_remote_branch( - &self.repo.borrow(), - &self.branches[self.selection as usize], - )?; - self.local = true; - self.update_branches()?; + self.queue.push(InternalEvent::CheckoutOption( + selected_branch.clone(), + self.local.clone() + )); } - self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); - Ok(()) } diff --git a/src/popups/checkout_option.rs b/src/popups/checkout_option.rs new file mode 100644 index 0000000000..05f4eff643 --- /dev/null +++ b/src/popups/checkout_option.rs @@ -0,0 +1,307 @@ +use crate::components::{ + visibility_blocking, CommandBlocking, CommandInfo, Component, + DrawableComponent, EventState, +}; +use crate::queue::{InternalEvent, NeedsUpdate}; +use crate::try_or_popup; +use crate::{ + app::Environment, + keys::{key_match, SharedKeyConfig}, + queue::Queue, + strings, + ui::{self, style::SharedTheme}, +}; +use anyhow::{Ok, Result}; +use asyncgit::sync::branch::checkout_remote_branch; +use asyncgit::sync::status::discard_status; +use asyncgit::sync::RepoPath; +use asyncgit::sync::{ + checkout_branch, stash_apply, stash_save, BranchInfo, +}; +use crossterm::event::Event; +use ratatui::{ + layout::{Alignment, Rect}, + text::{Line, Span}, + widgets::{Block, Borders, Clear, Paragraph}, + Frame, +}; + +#[derive(PartialEq, Clone, Copy)] +enum CheckoutOptions { + StashAndReapply, + Unchange, + Discard, +} + +const fn type_to_string( + kind: CheckoutOptions, +) -> (&'static str, &'static str) { + const CHECKOUT_OPTION_STASH_AND_REAPPLY: &str = + " 🟢 Stash and reapply changes"; + const CHECKOUT_OPTION_UNCHANGE: &str = " 🟡 Keep local changes"; + const CHECKOUT_OPTION_DISCARD: &str = + " 🔴 Discard all local changes"; + + match kind { + CheckoutOptions::StashAndReapply => { + ("Stash and reapply", CHECKOUT_OPTION_STASH_AND_REAPPLY) + } + CheckoutOptions::Unchange => { + ("Don't change", CHECKOUT_OPTION_UNCHANGE) + } + CheckoutOptions::Discard => { + ("Discard", CHECKOUT_OPTION_DISCARD) + } + } +} + +pub struct CheckoutOptionPopup { + queue: Queue, + repo: RepoPath, + local: bool, + branch: Option, + option: CheckoutOptions, + visible: bool, + key_config: SharedKeyConfig, + theme: SharedTheme, +} + +impl CheckoutOptionPopup { + /// + pub fn new(env: &Environment) -> Self { + Self { + queue: env.queue.clone(), + repo: env.repo.borrow().clone(), + local: false, + branch: None, + option: CheckoutOptions::StashAndReapply, + visible: false, + key_config: env.key_config.clone(), + theme: env.theme.clone(), + } + } + + fn get_text(&self, _width: u16) -> Vec { + let mut txt: Vec = Vec::with_capacity(10); + + txt.push(Line::from(vec![ + Span::styled( + String::from("Switch to: "), + self.theme.text(true, false), + ), + Span::styled( + self.branch.as_ref().unwrap().name.clone(), + self.theme.commit_hash(false), + ), + ])); + + let (kind_name, kind_desc) = type_to_string(self.option); + + txt.push(Line::from(vec![ + Span::styled( + String::from("How: "), + self.theme.text(true, false), + ), + Span::styled(kind_name, self.theme.text(true, true)), + Span::styled(kind_desc, self.theme.text(true, false)), + ])); + + txt + } + + /// + pub fn open( + &mut self, + branch: BranchInfo, + is_local: bool, + ) -> Result<()> { + self.show()?; + + self.branch = Some(branch); + self.local = is_local; + + Ok(()) + } + + fn checkout(&self) -> Result<()> { + if self.local { + checkout_branch( + &self.repo, + &self.branch.as_ref().unwrap().name, + )? + } else { + checkout_remote_branch( + &self.repo, + &self.branch.as_ref().unwrap(), + )?; + } + + Ok(()) + } + + fn handle_event(&mut self) -> Result<()> { + match self.option { + CheckoutOptions::StashAndReapply => { + let stash_id = stash_save( + &self.repo, + Some("Checkout auto stash"), + true, + false, + )?; + self.checkout()?; + stash_apply(&self.repo, stash_id, false)?; + } + CheckoutOptions::Unchange => { + self.checkout()?; + } + CheckoutOptions::Discard => { + discard_status(&self.repo)?; + self.checkout()?; + } + } + + self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); + self.queue.push(InternalEvent::SelectBranch); + self.hide(); + + Ok(()) + } + + fn change_kind(&mut self, incr: bool) { + self.option = if incr { + match self.option { + CheckoutOptions::StashAndReapply => { + CheckoutOptions::Unchange + } + CheckoutOptions::Unchange => CheckoutOptions::Discard, + CheckoutOptions::Discard => { + CheckoutOptions::StashAndReapply + } + } + } else { + match self.option { + CheckoutOptions::StashAndReapply => { + CheckoutOptions::Discard + } + CheckoutOptions::Unchange => { + CheckoutOptions::StashAndReapply + } + CheckoutOptions::Discard => CheckoutOptions::Unchange, + } + }; + } +} + +impl DrawableComponent for CheckoutOptionPopup { + fn draw(&self, f: &mut Frame, area: Rect) -> Result<()> { + if self.is_visible() { + const SIZE: (u16, u16) = (55, 4); + let area = + ui::centered_rect_absolute(SIZE.0, SIZE.1, area); + + let width = area.width; + + f.render_widget(Clear, area); + f.render_widget( + Paragraph::new(self.get_text(width)) + .block( + Block::default() + .borders(Borders::ALL) + .title(Span::styled( + "Checkout options", + self.theme.title(true), + )) + .border_style(self.theme.block(true)), + ) + .alignment(Alignment::Left), + area, + ); + } + + Ok(()) + } +} + +impl Component for CheckoutOptionPopup { + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + out.push( + CommandInfo::new( + strings::commands::close_popup(&self.key_config), + true, + true, + ) + .order(1), + ); + + out.push( + CommandInfo::new( + strings::commands::reset_commit(&self.key_config), + true, + true, + ) + .order(1), + ); + + out.push( + CommandInfo::new( + strings::commands::reset_type(&self.key_config), + true, + true, + ) + .order(1), + ); + } + + visibility_blocking(self) + } + + fn event( + &mut self, + event: &crossterm::event::Event, + ) -> Result { + if self.is_visible() { + if let Event::Key(key) = &event { + if key_match(key, self.key_config.keys.exit_popup) { + self.hide(); + } else if key_match( + key, + self.key_config.keys.move_down, + ) { + self.change_kind(true); + } else if key_match(key, self.key_config.keys.move_up) + { + self.change_kind(false); + } else if key_match(key, self.key_config.keys.enter) { + try_or_popup!( + self, + "checkout error:", + self.handle_event() + ); + } + } + + return Ok(EventState::Consumed); + } + + Ok(EventState::NotConsumed) + } + + 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/popups/mod.rs b/src/popups/mod.rs index cb3ae1af74..76390863be 100644 --- a/src/popups/mod.rs +++ b/src/popups/mod.rs @@ -27,6 +27,7 @@ mod submodules; mod tag_commit; mod taglist; mod update_remote_url; +mod checkout_option; pub use blame_file::{BlameFileOpen, BlameFilePopup}; pub use branchlist::BranchListPopup; @@ -57,6 +58,7 @@ pub use submodules::SubmodulesListPopup; pub use tag_commit::TagCommitPopup; pub use taglist::TagListPopup; pub use update_remote_url::UpdateRemoteUrlPopup; +pub use checkout_option::CheckoutOptionPopup; use crate::ui::style::Theme; use ratatui::{ diff --git a/src/queue.rs b/src/queue.rs index 44268a851d..fcbb6ddfb4 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -8,7 +8,8 @@ use crate::{ }; use asyncgit::{ sync::{ - diff::DiffLinePosition, CommitId, LogFilterSearchOptions, + diff::DiffLinePosition, BranchInfo, CommitId, + LogFilterSearchOptions, }, PushType, }; @@ -157,6 +158,8 @@ pub enum InternalEvent { RewordCommit(CommitId), /// CommitSearch(LogFilterSearchOptions), + /// + CheckoutOption(BranchInfo, bool), } /// single threaded simple queue for components to communicate with each other From 4c17a4b234ccbb508a151a21daf26a9b941a2e63 Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Mon, 20 Jan 2025 11:33:33 +0800 Subject: [PATCH 04/14] chore: fmt --- src/app.rs | 36 +++++++++++++++++------------------ src/popups/branchlist.rs | 2 +- src/popups/checkout_option.rs | 2 +- src/popups/mod.rs | 4 ++-- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/app.rs b/src/app.rs index ff7fc97fe2..89e87b4633 100644 --- a/src/app.rs +++ b/src/app.rs @@ -10,16 +10,16 @@ use crate::{ options::{Options, SharedOptions}, popup_stack::PopupStack, popups::{ - AppOption, BlameFilePopup, BranchListPopup, CommitPopup, - CompareCommitsPopup, ConfirmPopup, CreateBranchPopup, - CreateRemotePopup, ExternalEditorPopup, FetchPopup, - FileRevlogPopup, FuzzyFindPopup, HelpPopup, - InspectCommitPopup, LogSearchPopupPopup, MsgPopup, - OptionsPopup, PullPopup, PushPopup, PushTagsPopup, - RemoteListPopup, RenameBranchPopup, RenameRemotePopup, - ResetPopup, RevisionFilesPopup, StashMsgPopup, - SubmodulesListPopup, TagCommitPopup, TagListPopup, - UpdateRemoteUrlPopup, CheckoutOptionPopup + AppOption, BlameFilePopup, BranchListPopup, + CheckoutOptionPopup, CommitPopup, CompareCommitsPopup, + ConfirmPopup, CreateBranchPopup, CreateRemotePopup, + ExternalEditorPopup, FetchPopup, FileRevlogPopup, + FuzzyFindPopup, HelpPopup, InspectCommitPopup, + LogSearchPopupPopup, MsgPopup, OptionsPopup, PullPopup, + PushPopup, PushTagsPopup, RemoteListPopup, RenameBranchPopup, + RenameRemotePopup, ResetPopup, RevisionFilesPopup, + StashMsgPopup, SubmodulesListPopup, TagCommitPopup, + TagListPopup, UpdateRemoteUrlPopup, }, queue::{ Action, AppTabs, InternalEvent, NeedsUpdate, Queue, @@ -98,7 +98,7 @@ pub struct App { submodule_popup: SubmodulesListPopup, tags_popup: TagListPopup, reset_popup: ResetPopup, - checkout_option_popup: CheckoutOptionPopup, + checkout_option_popup: CheckoutOptionPopup, cmdbar: RefCell, tab: usize, revlog: Revlog, @@ -219,7 +219,7 @@ impl App { stashing_tab: Stashing::new(&env), stashlist_tab: StashList::new(&env), files_tab: FilesTab::new(&env), - checkout_option_popup: CheckoutOptionPopup::new(&env), + checkout_option_popup: CheckoutOptionPopup::new(&env), tab: 0, queue: env.queue, theme: env.theme, @@ -495,7 +495,7 @@ impl App { fetch_popup, tag_commit_popup, reset_popup, - checkout_option_popup, + checkout_option_popup, create_branch_popup, create_remote_popup, rename_remote_popup, @@ -536,7 +536,7 @@ impl App { submodule_popup, tags_popup, reset_popup, - checkout_option_popup, + checkout_option_popup, create_branch_popup, rename_branch_popup, revision_files_popup, @@ -908,10 +908,10 @@ impl App { } InternalEvent::CommitSearch(options) => { self.revlog.search(options); - }, - InternalEvent::CheckoutOption(branch, is_local) => { - self.checkout_option_popup.open(branch, is_local)?; - } + } + InternalEvent::CheckoutOption(branch, is_local) => { + self.checkout_option_popup.open(branch, is_local)?; + } }; Ok(flags) diff --git a/src/popups/branchlist.rs b/src/popups/branchlist.rs index 4d6a8d6c4b..b082fec2a0 100644 --- a/src/popups/branchlist.rs +++ b/src/popups/branchlist.rs @@ -610,7 +610,7 @@ impl BranchListPopup { } else { self.queue.push(InternalEvent::CheckoutOption( selected_branch.clone(), - self.local.clone() + self.local.clone(), )); } diff --git a/src/popups/checkout_option.rs b/src/popups/checkout_option.rs index 05f4eff643..64a2e6cbb1 100644 --- a/src/popups/checkout_option.rs +++ b/src/popups/checkout_option.rs @@ -152,7 +152,7 @@ impl CheckoutOptionPopup { stash_apply(&self.repo, stash_id, false)?; } CheckoutOptions::Unchange => { - self.checkout()?; + self.checkout()?; } CheckoutOptions::Discard => { discard_status(&self.repo)?; diff --git a/src/popups/mod.rs b/src/popups/mod.rs index 76390863be..f47f1e61ef 100644 --- a/src/popups/mod.rs +++ b/src/popups/mod.rs @@ -1,5 +1,6 @@ mod blame_file; mod branchlist; +mod checkout_option; mod commit; mod compare_commits; mod confirm; @@ -27,10 +28,10 @@ mod submodules; mod tag_commit; mod taglist; mod update_remote_url; -mod checkout_option; pub use blame_file::{BlameFileOpen, BlameFilePopup}; pub use branchlist::BranchListPopup; +pub use checkout_option::CheckoutOptionPopup; pub use commit::CommitPopup; pub use compare_commits::CompareCommitsPopup; pub use confirm::ConfirmPopup; @@ -58,7 +59,6 @@ pub use submodules::SubmodulesListPopup; pub use tag_commit::TagCommitPopup; pub use taglist::TagListPopup; pub use update_remote_url::UpdateRemoteUrlPopup; -pub use checkout_option::CheckoutOptionPopup; use crate::ui::style::Theme; use ratatui::{ From 96a41caca3a8bd0ed6cce37e2f7bf790e498ff98 Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Mon, 20 Jan 2025 11:56:22 +0800 Subject: [PATCH 05/14] fix: use stash pop to replace stash apply --- src/popups/checkout_option.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/popups/checkout_option.rs b/src/popups/checkout_option.rs index 64a2e6cbb1..113fd34760 100644 --- a/src/popups/checkout_option.rs +++ b/src/popups/checkout_option.rs @@ -14,10 +14,8 @@ use crate::{ use anyhow::{Ok, Result}; use asyncgit::sync::branch::checkout_remote_branch; use asyncgit::sync::status::discard_status; -use asyncgit::sync::RepoPath; -use asyncgit::sync::{ - checkout_branch, stash_apply, stash_save, BranchInfo, -}; +use asyncgit::sync::{checkout_branch, stash_save, BranchInfo}; +use asyncgit::sync::{stash_pop, RepoPath}; use crossterm::event::Event; use ratatui::{ layout::{Alignment, Rect}, @@ -149,7 +147,7 @@ impl CheckoutOptionPopup { false, )?; self.checkout()?; - stash_apply(&self.repo, stash_id, false)?; + stash_pop(&self.repo, stash_id)?; } CheckoutOptions::Unchange => { self.checkout()?; From 9cc2632edd001680575155eede6613bd062b6d1e Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Tue, 21 Jan 2025 16:57:44 +0800 Subject: [PATCH 06/14] chore: update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05823362cf..1b18030480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +* support choosing checkout branch method when status is not empty + ## [0.27.0] - 2024-01-14 **new: manage remotes** From b33c170079be634a0b6af74051c3b9631124403d Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Wed, 22 Jan 2025 08:58:43 +0800 Subject: [PATCH 07/14] chore: remove .editorconfig --- .editorconfig | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 3e3f23ae4a..0000000000 --- a/.editorconfig +++ /dev/null @@ -1,2 +0,0 @@ -[*.rs] -indent_style = tab From 4d222f3632d0a0811a8968ee91d4392046a1a75c Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Wed, 22 Jan 2025 09:01:02 +0800 Subject: [PATCH 08/14] chore: update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b18030480..61b556739f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -* support choosing checkout branch method when status is not empty +### Added +* support choosing checkout branch method when status is not empty [[@extrawurst](https://github.com/extrawurst)] ([#2404](https://github.com/extrawurst/gitui/issues/2404)) ## [0.27.0] - 2024-01-14 From 30cad4d7cbab4917898a886b4ebe9444cf3230f5 Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Wed, 22 Jan 2025 09:02:48 +0800 Subject: [PATCH 09/14] fix: merge import --- src/popups/branchlist.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/popups/branchlist.rs b/src/popups/branchlist.rs index b082fec2a0..2bcf108417 100644 --- a/src/popups/branchlist.rs +++ b/src/popups/branchlist.rs @@ -13,7 +13,6 @@ use crate::{ ui::{self, Size}, }; use anyhow::Result; -use asyncgit::sync::status::StatusType; use asyncgit::{ sync::{ self, @@ -21,8 +20,9 @@ use asyncgit::{ checkout_remote_branch, BranchDetails, LocalBranch, RemoteBranch, }, - checkout_branch, get_branches_info, BranchInfo, BranchType, - CommitId, RepoPathRef, RepoState, + checkout_branch, get_branches_info, + status::StatusType, + BranchInfo, BranchType, CommitId, RepoPathRef, RepoState, }, AsyncGitNotification, }; From bc3656fced6f8859f05f92235da2aae1add469c9 Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Wed, 22 Jan 2025 09:12:41 +0800 Subject: [PATCH 10/14] fix: move type_to_string func to strings.rs and rename --- src/popups/checkout_option.rs | 32 ++------------------------------ src/strings.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/popups/checkout_option.rs b/src/popups/checkout_option.rs index 113fd34760..4e7f531872 100644 --- a/src/popups/checkout_option.rs +++ b/src/popups/checkout_option.rs @@ -3,6 +3,7 @@ use crate::components::{ DrawableComponent, EventState, }; use crate::queue::{InternalEvent, NeedsUpdate}; +use crate::strings::{checkout_option_to_string, CheckoutOptions}; use crate::try_or_popup; use crate::{ app::Environment, @@ -24,35 +25,6 @@ use ratatui::{ Frame, }; -#[derive(PartialEq, Clone, Copy)] -enum CheckoutOptions { - StashAndReapply, - Unchange, - Discard, -} - -const fn type_to_string( - kind: CheckoutOptions, -) -> (&'static str, &'static str) { - const CHECKOUT_OPTION_STASH_AND_REAPPLY: &str = - " 🟢 Stash and reapply changes"; - const CHECKOUT_OPTION_UNCHANGE: &str = " 🟡 Keep local changes"; - const CHECKOUT_OPTION_DISCARD: &str = - " 🔴 Discard all local changes"; - - match kind { - CheckoutOptions::StashAndReapply => { - ("Stash and reapply", CHECKOUT_OPTION_STASH_AND_REAPPLY) - } - CheckoutOptions::Unchange => { - ("Don't change", CHECKOUT_OPTION_UNCHANGE) - } - CheckoutOptions::Discard => { - ("Discard", CHECKOUT_OPTION_DISCARD) - } - } -} - pub struct CheckoutOptionPopup { queue: Queue, repo: RepoPath, @@ -93,7 +65,7 @@ impl CheckoutOptionPopup { ), ])); - let (kind_name, kind_desc) = type_to_string(self.option); + let (kind_name, kind_desc) = checkout_option_to_string(self.option); txt.push(Line::from(vec![ Span::styled( diff --git a/src/strings.rs b/src/strings.rs index c4cff10f70..af26af5907 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -438,6 +438,35 @@ pub fn ellipsis_trim_start(s: &str, width: usize) -> Cow { } } +#[derive(PartialEq, Clone, Copy)] +pub enum CheckoutOptions { + StashAndReapply, + Unchange, + Discard, +} + +pub fn checkout_option_to_string( + kind: CheckoutOptions, +) -> (&'static str, &'static str) { + const CHECKOUT_OPTION_STASH_AND_REAPPLY: &str = + " 🟢 Stash and reapply changes"; + const CHECKOUT_OPTION_UNCHANGE: &str = " 🟡 Keep local changes"; + const CHECKOUT_OPTION_DISCARD: &str = + " 🔴 Discard all local changes"; + + match kind { + CheckoutOptions::StashAndReapply => { + ("Stash and reapply", CHECKOUT_OPTION_STASH_AND_REAPPLY) + } + CheckoutOptions::Unchange => { + ("Don't change", CHECKOUT_OPTION_UNCHANGE) + } + CheckoutOptions::Discard => { + ("Discard", CHECKOUT_OPTION_DISCARD) + } + } +} + pub mod commit { use crate::keys::SharedKeyConfig; From c44cd5a9af303da252d2ff73d8a746f27d4634cf Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Wed, 22 Jan 2025 09:23:47 +0800 Subject: [PATCH 11/14] test: add test for discard_status --- asyncgit/src/sync/status.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/asyncgit/src/sync/status.rs b/asyncgit/src/sync/status.rs index 1b33129545..7a22c70205 100644 --- a/asyncgit/src/sync/status.rs +++ b/asyncgit/src/sync/status.rs @@ -215,3 +215,34 @@ pub fn discard_status(repo_path: &RepoPath) -> Result { Ok(true) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::sync::tests::repo_init; + use std::{fs::File, io::Write, path::Path}; + + #[test] + fn test_discard_status() { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path: &RepoPath = + &root.as_os_str().to_str().unwrap().into(); + + File::create(root.join(file_path)) + .unwrap() + .write_all(b"test\nfoo") + .unwrap(); + + let statuses = get_status(repo_path, StatusType::WorkingDir, None) + .unwrap(); + assert_eq!(statuses.len(), 1); + + discard_status(repo_path).unwrap(); + + let statuses = get_status(repo_path, StatusType::WorkingDir, None) + .unwrap(); + assert_eq!(statuses.len(), 0); + } +} From e33fdd1431475495e58711cc39d4fb448ec19077 Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Wed, 22 Jan 2025 19:25:57 +0800 Subject: [PATCH 12/14] chore: update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61b556739f..74b3013728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added -* support choosing checkout branch method when status is not empty [[@extrawurst](https://github.com/extrawurst)] ([#2404](https://github.com/extrawurst/gitui/issues/2404)) +* support choosing checkout branch method when status is not empty [[@fatpandac](https://github.com/fatpandac)] ([#2404](https://github.com/extrawurst/gitui/issues/2404)) ## [0.27.0] - 2024-01-14 From 856ace5efeddc99acca47cc1ffe546abb130151c Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Sun, 2 Feb 2025 13:22:22 +0800 Subject: [PATCH 13/14] fix: change to another way to implement discarding status --- asyncgit/src/sync/status.rs | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/asyncgit/src/sync/status.rs b/asyncgit/src/sync/status.rs index 7a22c70205..11a80b1409 100644 --- a/asyncgit/src/sync/status.rs +++ b/asyncgit/src/sync/status.rs @@ -199,19 +199,9 @@ pub fn get_status( /// discard all changes in the working directory pub fn discard_status(repo_path: &RepoPath) -> Result { let repo = repo(repo_path)?; - let statuses = repo.statuses(None)?; + let commit = repo.head()?.peel_to_commit()?; - for status in statuses.iter() { - if status.status().is_wt_modified() - || status.status().is_wt_new() - { - let oid = repo.head()?.target().unwrap(); - let obj = repo.find_object(oid, None)?; - - repo.checkout_tree(&obj, None)?; - repo.reset(&obj, git2::ResetType::Hard, None)?; - } - } + repo.reset(commit.as_object(), git2::ResetType::Hard, None)?; Ok(true) } @@ -235,14 +225,16 @@ mod tests { .write_all(b"test\nfoo") .unwrap(); - let statuses = get_status(repo_path, StatusType::WorkingDir, None) - .unwrap(); - assert_eq!(statuses.len(), 1); + let statuses = + get_status(repo_path, StatusType::WorkingDir, None) + .unwrap(); + assert_eq!(statuses.len(), 1); - discard_status(repo_path).unwrap(); + discard_status(repo_path).unwrap(); - let statuses = get_status(repo_path, StatusType::WorkingDir, None) - .unwrap(); - assert_eq!(statuses.len(), 0); + let statuses = + get_status(repo_path, StatusType::WorkingDir, None) + .unwrap(); + assert_eq!(statuses.len(), 0); } } From 6cdca0934054ea7a45891c9e07a2f3b8861b3e0e Mon Sep 17 00:00:00 2001 From: Fatpandac Date: Sun, 2 Feb 2025 14:17:27 +0800 Subject: [PATCH 14/14] fix: changed to use default stash message --- src/popups/checkout_option.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/popups/checkout_option.rs b/src/popups/checkout_option.rs index 4e7f531872..9c40322a4a 100644 --- a/src/popups/checkout_option.rs +++ b/src/popups/checkout_option.rs @@ -15,8 +15,9 @@ use crate::{ use anyhow::{Ok, Result}; use asyncgit::sync::branch::checkout_remote_branch; use asyncgit::sync::status::discard_status; -use asyncgit::sync::{checkout_branch, stash_save, BranchInfo}; -use asyncgit::sync::{stash_pop, RepoPath}; +use asyncgit::sync::{ + checkout_branch, stash_pop, stash_save, BranchInfo, RepoPath, +}; use crossterm::event::Event; use ratatui::{ layout::{Alignment, Rect}, @@ -65,7 +66,8 @@ impl CheckoutOptionPopup { ), ])); - let (kind_name, kind_desc) = checkout_option_to_string(self.option); + let (kind_name, kind_desc) = + checkout_option_to_string(self.option); txt.push(Line::from(vec![ Span::styled( @@ -112,12 +114,8 @@ impl CheckoutOptionPopup { fn handle_event(&mut self) -> Result<()> { match self.option { CheckoutOptions::StashAndReapply => { - let stash_id = stash_save( - &self.repo, - Some("Checkout auto stash"), - true, - false, - )?; + let stash_id = + stash_save(&self.repo, None, true, false)?; self.checkout()?; stash_pop(&self.repo, stash_id)?; }