Fix: search in log (#1838)

This commit is contained in:
extrawurst 2023-08-26 20:34:37 +02:00 committed by GitHub
parent 5b2b8c7e0a
commit 0fdec134c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 207 additions and 200 deletions

View File

@ -2,7 +2,7 @@
.PHONY: debug build-release release-linux-musl test clippy clippy-pedantic install install-debug
ARGS=-l
# ARGS=-l -d ~/code/extern/pbrt-v4
# ARGS=-l -d ~/code/extern/linux
# ARGS=-l -d ~/code/git-bare-test.git -w ~/code/git-bare-test
profile:

View File

@ -1,7 +1,7 @@
use crate::{
error::Result,
sync::{repo, CommitId, LogWalker, LogWalkerFilter, RepoPath},
AsyncGitNotification,
AsyncGitNotification, Error,
};
use crossbeam_channel::Sender;
use scopetime::scope_time;
@ -15,7 +15,7 @@ use std::{
};
///
#[derive(PartialEq, Eq)]
#[derive(PartialEq, Eq, Debug)]
pub enum FetchStatus {
/// previous fetch still running
Pending,
@ -40,6 +40,7 @@ pub struct AsyncLog {
pending: Arc<AtomicBool>,
background: Arc<AtomicBool>,
filter: Option<LogWalkerFilter>,
partial_extract: AtomicBool,
repo: RepoPath,
}
@ -65,6 +66,7 @@ impl AsyncLog {
pending: Arc::new(AtomicBool::new(false)),
background: Arc::new(AtomicBool::new(false)),
filter,
partial_extract: AtomicBool::new(false),
}
}
@ -89,21 +91,26 @@ impl AsyncLog {
///
pub fn get_items(&self) -> Result<Vec<CommitId>> {
if self.partial_extract.load(Ordering::Relaxed) {
return Err(Error::Generic(String::from("Faulty usage of AsyncLog: Cannot partially extract items and rely on get_items slice to still work!")));
}
let list = &self.current.lock()?.commits;
Ok(list.clone())
}
///
pub fn get_last_duration(&self) -> Result<Duration> {
Ok(self.current.lock()?.duration)
pub fn extract_items(&self) -> Result<Vec<CommitId>> {
self.partial_extract.store(true, Ordering::Relaxed);
let list = &mut self.current.lock()?.commits;
let result = list.clone();
list.clear();
Ok(result)
}
///
pub fn position(&self, id: CommitId) -> Result<Option<usize>> {
let list = &self.current.lock()?.commits;
let position = list.iter().position(|&x| x == id);
Ok(position)
pub fn get_last_duration(&self) -> Result<Duration> {
Ok(self.current.lock()?.duration)
}
///
@ -143,6 +150,8 @@ impl AsyncLog {
return Ok(FetchStatus::NoChange);
}
self.pending.store(true, Ordering::Relaxed);
self.clear()?;
let arc_current = Arc::clone(&self.current);
@ -152,8 +161,6 @@ impl AsyncLog {
let filter = self.filter.clone();
let repo_path = self.repo.clone();
self.pending.store(true, Ordering::Relaxed);
if let Ok(head) = repo(&self.repo)?.head() {
*self.current_head.lock()? =
head.target().map(CommitId::new);
@ -192,17 +199,16 @@ impl AsyncLog {
let r = repo(repo_path)?;
let mut walker =
LogWalker::new(&r, LIMIT_COUNT)?.filter(filter);
loop {
entries.clear();
let res_is_err = walker.read(&mut entries).is_err();
let read = walker.read(&mut entries)?;
if !res_is_err {
let mut current = arc_current.lock()?;
current.commits.extend(entries.iter());
current.duration = start_time.elapsed();
}
let mut current = arc_current.lock()?;
current.commits.extend(entries.iter());
current.duration = start_time.elapsed();
if res_is_err || entries.len() <= 1 {
if read == 0 {
break;
}
Self::notify(sender);
@ -213,15 +219,19 @@ impl AsyncLog {
} else {
SLEEP_FOREGROUND
};
thread::sleep(sleep_duration);
}
log::trace!("revlog visited: {}", walker.visited());
Ok(())
}
fn clear(&mut self) -> Result<()> {
self.current.lock()?.commits.clear();
*self.current_head.lock()? = None;
self.partial_extract.store(false, Ordering::Relaxed);
Ok(())
}

View File

@ -261,6 +261,11 @@ impl<'a> LogWalker<'a> {
})
}
///
pub fn visited(&self) -> usize {
self.visited.len()
}
///
#[must_use]
pub fn filter(self, filter: Option<LogWalkerFilter>) -> Self {

View File

@ -13,7 +13,7 @@ use crate::{
};
use anyhow::Result;
use asyncgit::sync::{
checkout_commit, BranchDetails, BranchInfo, CommitId,
self, checkout_commit, BranchDetails, BranchInfo, CommitId,
RepoPathRef, Tags,
};
use chrono::{DateTime, Local};
@ -37,14 +37,16 @@ use std::{
};
const ELEMENTS_PER_LINE: usize = 9;
const SLICE_SIZE: usize = 1200;
///
pub struct CommitList {
repo: RepoPathRef,
title: Box<str>,
selection: usize,
count_total: usize,
items: ItemBatch,
highlights: Option<HashSet<CommitId>>,
commits: Vec<CommitId>,
marked: Vec<(usize, CommitId)>,
scroll_state: (Instant, f32),
tags: Option<Tags>,
@ -71,7 +73,8 @@ impl CommitList {
items: ItemBatch::default(),
marked: Vec::with_capacity(2),
selection: 0,
count_total: 0,
commits: Vec::new(),
highlights: None,
scroll_state: (Instant::now(), 0_f32),
tags: None,
local_branches: BTreeMap::default(),
@ -85,29 +88,6 @@ impl CommitList {
}
}
///
pub const fn selection(&self) -> usize {
self.selection
}
/// will return view size or None before the first render
pub fn current_size(&self) -> Option<(u16, u16)> {
self.current_size.get()
}
///
pub fn set_count_total(&mut self, total: usize) {
self.count_total = total;
self.selection =
cmp::min(self.selection, self.selection_max());
}
///
#[allow(clippy::missing_const_for_fn)]
pub fn selection_max(&self) -> usize {
self.count_total.saturating_sub(1)
}
///
pub const fn tags(&self) -> Option<&Tags> {
self.tags.as_ref()
@ -130,13 +110,6 @@ impl CommitList {
)
}
///
pub fn selected_entry_marked(&self) -> bool {
self.selected_entry()
.and_then(|e| self.is_marked(&e.id))
.unwrap_or_default()
}
///
pub fn marked_count(&self) -> usize {
self.marked.len()
@ -160,6 +133,7 @@ impl CommitList {
commits
}
///
pub fn copy_commit_hash(&self) -> Result<()> {
let marked = self.marked.as_slice();
let yank: Option<String> = match marked {
@ -201,6 +175,110 @@ impl CommitList {
Ok(())
}
///
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_local_branches(
&mut self,
local_branches: Vec<BranchInfo>,
) {
self.local_branches.clear();
for local_branch in local_branches {
self.local_branches
.entry(local_branch.top_commit)
.or_default()
.push(local_branch);
}
}
///
pub fn set_remote_branches(
&mut self,
remote_branches: Vec<BranchInfo>,
) {
self.remote_branches.clear();
for remote_branch in remote_branches {
self.remote_branches
.entry(remote_branch.top_commit)
.or_default()
.push(remote_branch);
}
}
///
pub fn set_commits(&mut self, commits: Vec<CommitId>) {
self.commits = commits;
self.fetch_commits();
}
///
pub fn refresh_extend_data(&mut self, commits: Vec<CommitId>) {
let new_commits = !commits.is_empty();
self.commits.extend(commits);
let selection = self.selection();
let selection_max = self.selection_max();
if self.needs_data(selection, selection_max) || new_commits {
self.fetch_commits();
}
}
///
pub fn set_highlighting(
&mut self,
highlighting: Option<HashSet<CommitId>>,
) {
self.highlights = highlighting;
self.select_next_highlight();
self.fetch_commits();
}
///
pub fn select_commit(&mut self, id: CommitId) -> Result<()> {
let position = self.commits.iter().position(|&x| x == id);
if let Some(position) = position {
self.selection = position;
Ok(())
} else {
anyhow::bail!("Could not select commit. It might not be loaded yet or it might be on a different branch.");
}
}
const fn selection(&self) -> usize {
self.selection
}
/// will return view size or None before the first render
fn current_size(&self) -> Option<(u16, u16)> {
self.current_size.get()
}
#[allow(clippy::missing_const_for_fn)]
fn selection_max(&self) -> usize {
self.commits.len().saturating_sub(1)
}
fn selected_entry_marked(&self) -> bool {
self.selected_entry()
.and_then(|e| self.is_marked(&e.id))
.unwrap_or_default()
}
fn move_selection(&mut self, scroll: ScrollType) -> Result<bool> {
let needs_update = if self.items.highlighting() {
self.move_selection_highlighting(scroll)?
@ -239,11 +317,7 @@ impl CommitList {
self.selection = new_selection;
if self
.selected_entry()
.map(|entry| entry.highlighted)
.unwrap_or_default()
{
if self.selection_highlighted() {
return Ok(true);
}
}
@ -562,65 +636,11 @@ impl CommitList {
self.selection.saturating_sub(self.items.index_offset())
}
pub fn select_entry(&mut self, position: usize) {
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_local_branches(
&mut self,
local_branches: Vec<BranchInfo>,
) {
self.local_branches.clear();
for local_branch in local_branches {
self.local_branches
.entry(local_branch.top_commit)
.or_default()
.push(local_branch);
}
}
pub fn set_remote_branches(
&mut self,
remote_branches: Vec<BranchInfo>,
) {
self.remote_branches.clear();
for remote_branch in remote_branches {
self.remote_branches
.entry(remote_branch.top_commit)
.or_default()
.push(remote_branch);
}
}
///
pub fn set_items(
&mut self,
start_index: usize,
commits: Vec<asyncgit::sync::CommitInfo>,
highlighted: &Option<HashSet<CommitId>>,
) {
self.items.set_items(start_index, commits, highlighted);
if self.items.highlighting() {
self.select_next_highlight();
}
}
fn select_next_highlight(&mut self) {
if self.highlights.is_none() {
return;
}
let old_selection = self.selection;
let mut offset = 0;
@ -655,15 +675,42 @@ impl CommitList {
}
fn selection_highlighted(&mut self) -> bool {
self.selected_entry()
.map(|entry| entry.highlighted)
let commit = self.commits[self.selection];
self.highlights
.as_ref()
.map(|highlights| highlights.contains(&commit))
.unwrap_or_default()
}
///
pub fn needs_data(&self, idx: usize, idx_max: usize) -> bool {
fn needs_data(&self, idx: usize, idx_max: usize) -> bool {
self.items.needs_data(idx, idx_max)
}
fn fetch_commits(&mut self) {
let want_min =
self.selection().saturating_sub(SLICE_SIZE / 2);
let commits = self.commits.len();
let want_min = want_min.min(commits);
let slice_end =
want_min.saturating_add(SLICE_SIZE).min(commits);
let commits = sync::get_commits_info(
&self.repo.borrow(),
&self.commits[want_min..slice_end],
self.current_size().map_or(100u16, |size| size.0).into(),
);
if let Ok(commits) = commits {
self.items.set_items(
want_min,
commits,
//TODO: optimize via sharable data (BTreeMap that preserves order and lookup)
&self.highlights.clone(),
);
}
}
}
impl DrawableComponent for CommitList {
@ -690,8 +737,8 @@ impl DrawableComponent for CommitList {
let title = format!(
"{} {}/{}",
self.title,
self.count_total.saturating_sub(self.selection),
self.count_total,
self.commits.len().saturating_sub(self.selection),
self.commits.len(),
);
f.render_widget(
@ -718,7 +765,7 @@ impl DrawableComponent for CommitList {
f,
area,
&self.theme,
self.count_total,
self.commits.len(),
self.selection,
Orientation::Vertical,
);

View File

@ -113,8 +113,6 @@ impl ItemBatch {
commits: Vec<CommitInfo>,
highlighted: &Option<HashSet<CommitId>>,
) {
log::debug!("highlighted: {:?}", highlighted);
self.items.clear();
self.items.extend(commits.into_iter().map(|c| {
let id = c.id;

View File

@ -33,10 +33,8 @@ use ratatui::{
use std::{collections::HashSet, rc::Rc, time::Duration};
use sync::CommitTags;
const SLICE_SIZE: usize = 1200;
struct LogSearchResult {
commits: Vec<CommitId>,
commits: usize,
options: LogFilterSearchOptions,
duration: Duration,
}
@ -132,22 +130,15 @@ impl Revlog {
///
pub fn update(&mut self) -> Result<()> {
if self.is_visible() {
let log_changed =
self.git_log.fetch()? == FetchStatus::Started;
let search_changed = self.update_search_state()?;
let log_changed = log_changed || search_changed;
self.list.set_count_total(self.git_log.count()?);
let selection = self.list.selection();
let selection_max = self.list.selection_max();
if self.list.needs_data(selection, selection_max)
|| log_changed
{
self.fetch_commits()?;
if self.git_log.fetch()? == FetchStatus::Started {
self.list.clear();
}
self.update_search_state()?;
self.list
.refresh_extend_data(self.git_log.extract_items()?);
self.git_tags.request(Duration::from_secs(3), false)?;
if self.commit_details.is_visible() {
@ -211,27 +202,6 @@ impl Revlog {
Ok(())
}
fn fetch_commits(&mut self) -> Result<()> {
let want_min =
self.list.selection().saturating_sub(SLICE_SIZE / 2);
let commits = sync::get_commits_info(
&self.repo.borrow(),
&self.git_log.get_slice(want_min, SLICE_SIZE)?,
self.list
.current_size()
.map_or(100u16, |size| size.0)
.into(),
);
if let Ok(commits) = commits {
let highlighted = self.search_result_set();
self.list.set_items(want_min, commits, &highlighted);
}
Ok(())
}
fn selected_commit(&self) -> Option<CommitId> {
self.list.selected_entry().map(|e| e.id)
}
@ -247,16 +217,9 @@ impl Revlog {
})
}
///
pub fn select_commit(&mut self, id: CommitId) -> Result<()> {
let position = self.git_log.position(id)?;
if let Some(position) = position {
self.list.select_entry(position);
Ok(())
} else {
anyhow::bail!("Could not select commit in revlog. It might not be loaded yet or it might be on a different branch.");
}
self.list.select_commit(id)
}
fn revert_commit(&self) -> Result<()> {
@ -299,30 +262,16 @@ impl Revlog {
Some(filter),
);
async_find.fetch()?;
assert_eq!(async_find.fetch()?, FetchStatus::Started);
self.search = LogSearch::Searching(async_find, options);
self.fetch_commits()?;
self.list.set_highlighting(None);
}
Ok(())
}
fn search_result_set(&self) -> Option<HashSet<CommitId>> {
if let LogSearch::Results(results) = &self.search {
Some(
results
.commits
.iter()
.map(CommitId::clone)
.collect::<HashSet<_>>(),
)
} else {
None
}
}
fn update_search_state(&mut self) -> Result<bool> {
let changes = match &self.search {
LogSearch::Off | LogSearch::Results(_) => false,
@ -330,11 +279,17 @@ impl Revlog {
if search.is_pending() {
false
} else {
let results = search.get_items()?;
let results = search.extract_items()?;
let commits = results.len();
let duration = search.get_last_duration()?;
self.list.set_highlighting(Some(
results.into_iter().collect::<HashSet<_>>(),
));
self.search =
LogSearch::Results(LogSearchResult {
commits: results,
commits,
options: options.clone(),
duration,
});
@ -350,7 +305,6 @@ impl Revlog {
!matches!(self.search, LogSearch::Off)
}
//TODO: draw time a search took
fn draw_search<B: Backend>(&self, f: &mut Frame<B>, area: Rect) {
let text = match &self.search {
LogSearch::Searching(_, options) => {
@ -363,7 +317,7 @@ impl Revlog {
format!(
"'{}' (hits: {}) (duration: {:?})",
results.options.search_pattern.clone(),
results.commits.len(),
results.commits,
results.duration,
)
}
@ -456,7 +410,7 @@ impl Component for Revlog {
) {
if self.can_leave_search() {
self.search = LogSearch::Off;
self.fetch_commits()?;
self.list.set_highlighting(None);
return Ok(EventState::Consumed);
}
} else if key_match(k, self.key_config.keys.copy) {

View File

@ -48,14 +48,7 @@ impl StashList {
pub fn update(&mut self) -> Result<()> {
if self.is_visible() {
let stashes = sync::get_stashes(&self.repo.borrow())?;
let commits = sync::get_commits_info(
&self.repo.borrow(),
stashes.as_slice(),
100,
)?;
self.list.set_count_total(commits.len());
self.list.set_items(0, commits, &None);
self.list.set_commits(stashes);
}
Ok(())