summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrey Krupskiy <krupskiy.andrey@gmail.com>2023-02-04 07:00:19 +0100
committerGitHub <noreply@github.com>2023-02-04 07:00:19 +0100
commit57a5322fa76baf8c5bf66db533419b13297d0057 (patch)
tree834e1b72f6c8c5f8a9ba87140d67b7bb03bd349d
parent5411397f9a642e2268903e3a78a264f5c11afb80 (diff)
Checkout commit (#1499)
* Add keybind to checkout commit in log view * Extract commit checkout into method * add quckbar hint for checkout commit * add a smoke test * update changelog * show an error in popup --------- Co-authored-by: Omnikar <omnikar5@gmail.com> Co-authored-by: extrawurst <776816+extrawurst@users.noreply.github.com>
-rw-r--r--CHANGELOG.md1
-rw-r--r--asyncgit/src/sync/branch/mod.rs57
-rw-r--r--asyncgit/src/sync/mod.rs7
-rw-r--r--src/components/commitlist.rs26
-rw-r--r--src/keys/key_list.rs2
-rw-r--r--src/keys/key_list_file.rs2
-rw-r--r--src/strings.rs13
-rw-r--r--src/tabs/revlog.rs7
-rw-r--r--src/tabs/stashlist.rs1
9 files changed, 112 insertions, 4 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ff5d7329..523ba413 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
* changes in commit message inside external editor [[@bc-universe]](https://github.com/bc-universe) ([#1420](https://github.com/extrawurst/gitui/issues/1420))
+* allow detaching HEAD and checking out specific commit from log view ([#1499](https://github.com/extrawurst/gitui/pull/1499))
* add no-verify option on commits to not run hooks [[@dam5h]](https://github.com/dam5h) ([#1374](https://github.com/extrawurst/gitui/issues/1374))
* allow `fetch` on status tab [[@alensiljak]](https://github.com/alensiljak) ([#1471](https://github.com/extrawurst/gitui/issues/1471))
diff --git a/asyncgit/src/sync/branch/mod.rs b/asyncgit/src/sync/branch/mod.rs
index c72a4845..89056ecf 100644
--- a/asyncgit/src/sync/branch/mod.rs
+++ b/asyncgit/src/sync/branch/mod.rs
@@ -301,6 +301,36 @@ pub fn checkout_branch(
}
}
+/// Detach HEAD to point to a commit then checkout HEAD, does not work if there are uncommitted changes
+pub fn checkout_commit(
+ repo_path: &RepoPath,
+ commit_hash: CommitId,
+) -> Result<()> {
+ scope_time!("checkout_commit");
+
+ let repo = repo(repo_path)?;
+ let cur_ref = repo.head()?;
+ let statuses = repo.statuses(Some(
+ git2::StatusOptions::new().include_ignored(false),
+ ))?;
+
+ if statuses.is_empty() {
+ repo.set_head_detached(commit_hash.into())?;
+
+ if let Err(e) = repo.checkout_head(Some(
+ git2::build::CheckoutBuilder::new().force(),
+ )) {
+ repo.set_head(
+ bytes2string(cur_ref.name_bytes())?.as_str(),
+ )?;
+ return Err(Error::Git(e));
+ }
+ Ok(())
+ } else {
+ Err(Error::UncommittedChanges)
+ }
+}
+
///
pub fn checkout_remote_branch(
repo_path: &RepoPath,
@@ -666,6 +696,33 @@ mod tests_checkout {
}
#[cfg(test)]
+mod tests_checkout_commit {
+ use super::*;
+ use crate::sync::tests::{repo_init, write_commit_file};
+ use crate::sync::RepoPath;
+
+ #[test]
+ fn test_smoke() {
+ let (_td, repo) = repo_init().unwrap();
+ let root = repo.path().parent().unwrap();
+ let repo_path: &RepoPath =
+ &root.as_os_str().to_str().unwrap().into();
+
+ let commit =
+ write_commit_file(&repo, "test_1.txt", "test", "commit1");
+ write_commit_file(&repo, "test_2.txt", "test", "commit2");
+
+ checkout_commit(repo_path, commit).unwrap();
+
+ assert!(repo.head_detached().unwrap());
+ assert_eq!(
+ repo.head().unwrap().target().unwrap(),
+ commit.get_oid()
+ );
+ }
+}
+
+#[cfg(test)]
mod test_delete_branch {
use super::*;
use crate::sync::tests::repo_init;
diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs
index ed305b4a..9194bf03 100644
--- a/asyncgit/src/sync/mod.rs
+++ b/asyncgit/src/sync/mod.rs
@@ -34,9 +34,10 @@ pub mod utils;
pub use blame::{blame_file, BlameHunk, FileBlame};
pub use branch::{
- branch_compare_upstream, checkout_branch, config_is_pull_rebase,
- create_branch, delete_branch, get_branch_remote,
- get_branches_info, merge_commit::merge_upstream_commit,
+ branch_compare_upstream, checkout_branch, checkout_commit,
+ config_is_pull_rebase, create_branch, delete_branch,
+ get_branch_remote, get_branches_info,
+ merge_commit::merge_upstream_commit,
merge_ff::branch_merge_upstream_fastforward,
merge_rebase::merge_upstream_rebase, rename::rename_branch,
validate_branch_name, BranchCompare, BranchInfo,
diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs
index 2d2a42b3..603c5332 100644
--- a/src/components/commitlist.rs
+++ b/src/components/commitlist.rs
@@ -7,11 +7,14 @@ use crate::{
keys::{key_match, SharedKeyConfig},
queue::{InternalEvent, Queue},
strings::{self, symbol},
+ try_or_popup,
ui::style::{SharedTheme, Theme},
ui::{calc_scroll_top, draw_scrollbar, Orientation},
};
use anyhow::Result;
-use asyncgit::sync::{BranchInfo, CommitId, Tags};
+use asyncgit::sync::{
+ checkout_commit, BranchInfo, CommitId, RepoPathRef, Tags,
+};
use chrono::{DateTime, Local};
use crossterm::event::Event;
use itertools::Itertools;
@@ -31,6 +34,7 @@ const ELEMENTS_PER_LINE: usize = 9;
///
pub struct CommitList {
+ repo: RepoPathRef,
title: Box<str>,
selection: usize,
count_total: usize,
@@ -49,12 +53,14 @@ pub struct CommitList {
impl CommitList {
///
pub fn new(
+ repo: RepoPathRef,
title: &str,
theme: SharedTheme,
queue: Queue,
key_config: SharedKeyConfig,
) -> Self {
Self {
+ repo,
items: ItemBatch::default(),
marked: Vec::with_capacity(2),
selection: 0,
@@ -435,6 +441,18 @@ impl CommitList {
self.selection = position;
}
+ pub fn checkout(&mut self) {
+ if let Some(commit_hash) =
+ self.selected_entry().map(|entry| entry.id)
+ {
+ try_or_popup!(
+ self,
+ "failed to checkout commit:",
+ checkout_commit(&self.repo.borrow(), commit_hash)
+ );
+ }
+ }
+
pub fn set_branches(&mut self, branches: Vec<BranchInfo>) {
self.branches.clear();
@@ -538,6 +556,12 @@ impl Component for CommitList {
) {
self.mark();
true
+ } else if key_match(
+ k,
+ self.key_config.keys.log_checkout_commit,
+ ) {
+ self.checkout();
+ true
} else {
false
};
diff --git a/src/keys/key_list.rs b/src/keys/key_list.rs
index d1120549..96fbaeeb 100644
--- a/src/keys/key_list.rs
+++ b/src/keys/key_list.rs
@@ -87,6 +87,7 @@ pub struct KeysList {
pub cmd_bar_toggle: GituiKeyEvent,
pub log_tag_commit: GituiKeyEvent,
pub log_mark_commit: GituiKeyEvent,
+ pub log_checkout_commit: GituiKeyEvent,
pub commit_amend: GituiKeyEvent,
pub toggle_verify: GituiKeyEvent,
pub copy: GituiKeyEvent,
@@ -171,6 +172,7 @@ impl Default for KeysList {
cmd_bar_toggle: GituiKeyEvent::new(KeyCode::Char('.'), KeyModifiers::empty()),
log_tag_commit: GituiKeyEvent::new(KeyCode::Char('t'), KeyModifiers::empty()),
log_mark_commit: GituiKeyEvent::new(KeyCode::Char(' '), KeyModifiers::empty()),
+ log_checkout_commit: GituiKeyEvent { code: KeyCode::Char('S'), modifiers: KeyModifiers::SHIFT },
commit_amend: GituiKeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL),
toggle_verify: GituiKeyEvent::new(KeyCode::Char('f'), KeyModifiers::CONTROL),
copy: GituiKeyEvent::new(KeyCode::Char('y'), KeyModifiers::empty()),
diff --git a/src/keys/key_list_file.rs b/src/keys/key_list_file.rs
index b73f87bf..ae8b4f2f 100644
--- a/src/keys/key_list_file.rs
+++ b/src/keys/key_list_file.rs
@@ -58,6 +58,7 @@ pub struct KeysListFile {
pub cmd_bar_toggle: Option<GituiKeyEvent>,
pub log_tag_commit: Option<GituiKeyEvent>,
pub log_mark_commit: Option<GituiKeyEvent>,
+ pub log_checkout_commit: Option<GituiKeyEvent>,
pub commit_amend: Option<GituiKeyEvent>,
pub toggle_verify: Option<GituiKeyEvent>,
pub copy: Option<GituiKeyEvent>,
@@ -151,6 +152,7 @@ impl KeysListFile {
cmd_bar_toggle: self.cmd_bar_toggle.unwrap_or(default.cmd_bar_toggle),
log_tag_commit: self.log_tag_commit.unwrap_or(default.log_tag_commit),
log_mark_commit: self.log_mark_commit.unwrap_or(default.log_mark_commit),
+ log_checkout_commit: self.log_checkout_commit.unwrap_or(default.log_checkout_commit),
commit_amend: self.commit_amend.unwrap_or(default.commit_amend),
toggle_verify: self.toggle_verify.unwrap_or(default.toggle_verify),
copy: self.copy.unwrap_or(default.copy),
diff --git a/src/strings.rs b/src/strings.rs
index 561a853b..37609499 100644
--- a/src/strings.rs
+++ b/src/strings.rs
@@ -1195,6 +1195,19 @@ pub mod commands {
CMD_GROUP_LOG,
)
}
+ pub fn log_checkout_commit(
+ key_config: &SharedKeyConfig,
+ ) -> CommandText {
+ CommandText::new(
+ format!(
+ "Checkout [{}]",
+ key_config
+ .get_hint(key_config.keys.log_checkout_commit),
+ ),
+ "checkout commit",
+ CMD_GROUP_LOG,
+ )
+ }
pub fn inspect_file_tree(
key_config: &SharedKeyConfig,
) -> CommandText {
diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs
index fd124025..0b0ad7d4 100644
--- a/src/tabs/revlog.rs
+++ b/src/tabs/revlog.rs
@@ -62,6 +62,7 @@ impl Revlog {
key_config.clone(),
),
list: CommitList::new(
+ repo.clone(),
&strings::log_title(&key_config),
theme,
queue.clone(),
@@ -419,6 +420,12 @@ impl Component for Revlog {
));
out.push(CommandInfo::new(
+ strings::commands::log_checkout_commit(&self.key_config),
+ self.selected_commit().is_some(),
+ self.visible || force_all,
+ ));
+
+ out.push(CommandInfo::new(
strings::commands::open_tags_popup(&self.key_config),
true,
self.visible || force_all,
diff --git a/src/tabs/stashlist.rs b/src/tabs/stashlist.rs
index 89176e93..4a9e1449 100644
--- a/src/tabs/stashlist.rs
+++ b/src/tabs/stashlist.rs
@@ -32,6 +32,7 @@ impl StashList {
Self {
visible: false,
list: CommitList::new(
+ repo.clone(),
&strings::stashlist_title(&key_config),
theme,
queue.clone(),