mirror of
https://github.com/extrawurst/gitui.git
synced 2024-12-27 11:03:03 +03:00
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>
This commit is contained in:
parent
5411397f9a
commit
57a5322fa7
@ -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))
|
||||
|
||||
|
@ -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,
|
||||
@ -665,6 +695,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::*;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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()),
|
||||
|
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -62,6 +62,7 @@ impl Revlog {
|
||||
key_config.clone(),
|
||||
),
|
||||
list: CommitList::new(
|
||||
repo.clone(),
|
||||
&strings::log_title(&key_config),
|
||||
theme,
|
||||
queue.clone(),
|
||||
@ -418,6 +419,12 @@ impl Component for Revlog {
|
||||
self.visible || force_all,
|
||||
));
|
||||
|
||||
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,
|
||||
|
@ -32,6 +32,7 @@ impl StashList {
|
||||
Self {
|
||||
visible: false,
|
||||
list: CommitList::new(
|
||||
repo.clone(),
|
||||
&strings::stashlist_title(&key_config),
|
||||
theme,
|
||||
queue.clone(),
|
||||
|
Loading…
Reference in New Issue
Block a user