From a4815318658ac805f36d8addd82e7cf8c7bf0c05 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:03:19 -0400 Subject: [PATCH 01/60] add: graph glyphs --- asyncgit/Cargo.toml | 2 ++ src/components/utils/graphrow.rs | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 src/components/utils/graphrow.rs diff --git a/asyncgit/Cargo.toml b/asyncgit/Cargo.toml index d4e9fad37f..d46fecf6ff 100644 --- a/asyncgit/Cargo.toml +++ b/asyncgit/Cargo.toml @@ -20,6 +20,7 @@ fuzzy-matcher = "0.3" git2 = { version = "0.21", features = ["https"] } git2-hooks = { path = "../git2-hooks", version = "0.7" } gix = { version = "0.78.0", default-features = false, features = ["mailmap", "max-performance", "revision", "status"] } +im = "15" log = "0.4" # git2 = { path = "../../extern/git2-rs", features = ["vendored-openssl"]} # git2 = { git="https://github.com/extrawurst/git2-rs.git", rev="fc13dcc", features = ["vendored-openssl"]} @@ -29,6 +30,7 @@ rayon = "1.11" rayon-core = "1.13" scopetime = { path = "../scopetime", version = "0.1" } serde = { version = "1.0", features = ["derive"] } +smallvec = "1" ssh-key = { version = "0.6.7", features = ["crypto", "encryption"] } thiserror = "2.0" unicode-truncate = "2.0" diff --git a/src/components/utils/graphrow.rs b/src/components/utils/graphrow.rs new file mode 100644 index 0000000000..f5d9aef8be --- /dev/null +++ b/src/components/utils/graphrow.rs @@ -0,0 +1,15 @@ +pub const SYM_COMMIT: &str = "○"; +pub const SYM_COMMIT_BRANCH: &str = "●"; +pub const SYM_COMMIT_MERGE: &str = "•"; +pub const SYM_COMMIT_STASH: &str = "◎"; +pub const SYM_COMMIT_UNCOMMITTED: &str = "◌"; +pub const SYM_VERTICAL: &str = "│"; +pub const SYM_VERTICAL_DOTTED: &str = "┊"; +pub const SYM_HORIZONTAL: &str = "─"; +pub const SYM_MERGE_BRIDGE_START: &str = "╮"; +pub const SYM_MERGE_BRIDGE_MID: &str = "─"; +pub const SYM_MERGE_BRIDGE_END: &str = "╭"; +pub const SYM_BRANCH_UP: &str = "╯"; +pub const SYM_BRANCH_DOWN: &str = "╮"; +pub const SYM_BRANCH_UP_RIGHT: &str = "╰"; +pub const SYM_SPACE: &str = " "; From 64291d1ede3dfac9384299dbf9d37fde6891dccd Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:04:08 -0400 Subject: [PATCH 02/60] fix: moving to graph glyphs that fit GITUIs style more --- src/components/utils/graphrow.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/utils/graphrow.rs b/src/components/utils/graphrow.rs index f5d9aef8be..db44162a1b 100644 --- a/src/components/utils/graphrow.rs +++ b/src/components/utils/graphrow.rs @@ -1,15 +1,15 @@ -pub const SYM_COMMIT: &str = "○"; -pub const SYM_COMMIT_BRANCH: &str = "●"; -pub const SYM_COMMIT_MERGE: &str = "•"; -pub const SYM_COMMIT_STASH: &str = "◎"; -pub const SYM_COMMIT_UNCOMMITTED: &str = "◌"; -pub const SYM_VERTICAL: &str = "│"; -pub const SYM_VERTICAL_DOTTED: &str = "┊"; -pub const SYM_HORIZONTAL: &str = "─"; -pub const SYM_MERGE_BRIDGE_START: &str = "╮"; -pub const SYM_MERGE_BRIDGE_MID: &str = "─"; -pub const SYM_MERGE_BRIDGE_END: &str = "╭"; -pub const SYM_BRANCH_UP: &str = "╯"; -pub const SYM_BRANCH_DOWN: &str = "╮"; -pub const SYM_BRANCH_UP_RIGHT: &str = "╰"; +pub const SYM_COMMIT: &str = "◆"; +pub const SYM_COMMIT_BRANCH: &str = "◈"; +pub const SYM_COMMIT_MERGE: &str = "◇"; +pub const SYM_COMMIT_STASH: &str = "⟡"; +pub const SYM_COMMIT_UNCOMMITTED: &str = "◻"; +pub const SYM_VERTICAL: &str = "┃"; +pub const SYM_VERTICAL_DOTTED: &str = "╏"; +pub const SYM_HORIZONTAL: &str = "━"; +pub const SYM_MERGE_BRIDGE_START: &str = "┓"; +pub const SYM_MERGE_BRIDGE_MID: &str = "━"; +pub const SYM_MERGE_BRIDGE_END: &str = "┏"; +pub const SYM_BRANCH_UP: &str = "┛"; +pub const SYM_BRANCH_DOWN: &str = "┓"; +pub const SYM_BRANCH_UP_RIGHT: &str = "┗"; pub const SYM_SPACE: &str = " "; From d3586a32f1cdcf38a3bd84d66108a409beae01d7 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:06:35 -0400 Subject: [PATCH 03/60] add: buffer datastructure to hold graph representation --- asyncgit/src/graph/buffer.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 asyncgit/src/graph/buffer.rs diff --git a/asyncgit/src/graph/buffer.rs b/asyncgit/src/graph/buffer.rs new file mode 100644 index 0000000000..d47eb8cdbb --- /dev/null +++ b/asyncgit/src/graph/buffer.rs @@ -0,0 +1,30 @@ +use super::chunk::{Chunk, Markers}; +use im::Vector; +use std::collections::BTreeMap; + +#[derive(Clone, Debug)] +pub enum DeltaOp { + Insert { index: usize, item: Option }, + Remove { index: usize }, + Replace { index: usize, new: Option }, +} + +#[derive(Clone, Debug)] +pub struct Delta(pub Vec); + +const CHECKPOINT_INTERVAL: usize = 100; + +pub struct Buffer { + pub current: Vector>, + pub deltas: Vec, + pub checkpoints: BTreeMap>>, + mergers: Vec, + pending_delta: Vec, +} + +impl Default for Buffer { + fn default() -> Self { + Self::new() + } +} + From 71dd2cc4c4365a4dbb14520b01a1892200f8a021 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:07:04 -0400 Subject: [PATCH 04/60] add: buffer methods to facilitate updates against the graph --- asyncgit/src/graph/buffer.rs | 198 +++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) diff --git a/asyncgit/src/graph/buffer.rs b/asyncgit/src/graph/buffer.rs index d47eb8cdbb..88fd12b2c2 100644 --- a/asyncgit/src/graph/buffer.rs +++ b/asyncgit/src/graph/buffer.rs @@ -28,3 +28,201 @@ impl Default for Buffer { } } +impl Buffer { + pub fn new() -> Self { + Self { + current: Vector::new(), + deltas: Vec::new(), + checkpoints: BTreeMap::new(), + mergers: Vec::new(), + pending_delta: Vec::new(), + } + } + + pub fn merger(&mut self, alias: u32) { + self.mergers.push(alias); + } + + pub fn update(&mut self, new_chunk: Chunk) { + self.pending_delta.clear(); + + let mut found_idx = None; + if new_chunk.alias.is_some() { + for (i, c) in self.current.iter().enumerate() { + if let Some(c) = c { + if c.parent_a == new_chunk.alias { + found_idx = Some(i); + break; + } + } + } + } + + if let Some(idx) = found_idx { + self.record_replace(idx, Some(new_chunk.clone())); + } else { + self.record_insert( + self.current.len(), + Some(new_chunk.clone()), + ); + } + + let current_length = self.current.len(); + for index in 0..current_length { + if Some(index) == found_idx { + continue; + } + if found_idx.is_none() && index == current_length - 1 { + continue; + } + + if let Some(mut c) = self.current[index].clone() { + let mut changed = false; + + if new_chunk.alias.is_some() + && c.parent_a == new_chunk.alias + { + c.parent_a = None; + changed = true; + } + if new_chunk.alias.is_some() + && c.parent_b == new_chunk.alias + { + c.parent_b = None; + changed = true; + } + + if changed { + if c.parent_a.is_none() && c.parent_b.is_none() { + self.record_replace(index, None); + } else { + self.record_replace(index, Some(c)); + } + } + } + } + + while let Some(alias) = self.mergers.pop() { + if let Some(index) = self.current.iter().position(|c| { + c.as_ref() + .map_or(false, |chunk| chunk.alias == Some(alias)) + }) { + if let Some(mut c) = self.current[index].clone() { + let parent_b = c.parent_b; + c.parent_b = None; + self.record_replace(index, Some(c)); + + let new_lane = Chunk { + alias: None, + parent_a: parent_b, + parent_b: None, + marker: Markers::Commit, + }; + self.record_insert( + self.current.len(), + Some(new_lane), + ); + } + } + } + + loop { + if let Some(last) = self.current.last() { + if last.is_none() { + self.record_remove(self.current.len() - 1); + continue; + } + } + break; + } + + let delta = Delta(self.pending_delta.clone()); + self.deltas.push(delta); + + let current_step = self.deltas.len(); + if current_step > 0 && current_step % CHECKPOINT_INTERVAL == 0 + { + self.checkpoints + .insert(current_step - 1, self.current.clone()); + } + } + + fn record_replace(&mut self, index: usize, new: Option) { + self.pending_delta.push(DeltaOp::Replace { + index, + new: new.clone(), + }); + self.current.set(index, new); + } + + fn record_insert(&mut self, index: usize, item: Option) { + self.pending_delta.push(DeltaOp::Insert { + index, + item: item.clone(), + }); + self.current.insert(index, item); + } + + fn record_remove(&mut self, index: usize) { + self.pending_delta.push(DeltaOp::Remove { index }); + self.current.remove(index); + } + + pub fn decompress( + &self, + start: usize, + end: usize, + ) -> Vec>> { + let (current_index, mut state) = self + .checkpoints + .range(..=start) + .next_back() + .map(|(&i, s)| (Some(i), s.clone())) + .unwrap_or((None, Vector::new())); + + let mut history = + Vec::with_capacity(end.saturating_sub(start) + 1); + + if let Some(index) = current_index { + if index >= start && index <= end { + history.push(state.clone()); + } + } + + let loop_start = current_index.map(|i| i + 1).unwrap_or(0); + + for delta_index in loop_start..=end { + if let Some(delta) = self.deltas.get(delta_index) { + self.apply_delta_to_state(&mut state, delta); + + if delta_index >= start { + history.push(state.clone()); + } + } else { + break; + } + } + + history + } + + fn apply_delta_to_state( + &self, + state: &mut Vector>, + delta: &Delta, + ) { + for op in &delta.0 { + match op { + DeltaOp::Insert { index, item } => { + state.insert(*index, item.clone()) + } + DeltaOp::Remove { index } => { + state.remove(*index); + } + DeltaOp::Replace { index, new } => { + state.set(*index, new.clone()); + } + } + } + } +} From e39ce8a9e913cdaa18e52ebe063cb6106bb279d3 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:07:28 -0400 Subject: [PATCH 05/60] add: chunk datastructure to hold particular bits of the graph --- asyncgit/src/graph/chunk.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 asyncgit/src/graph/chunk.rs diff --git a/asyncgit/src/graph/chunk.rs b/asyncgit/src/graph/chunk.rs new file mode 100644 index 0000000000..ae74013cfc --- /dev/null +++ b/asyncgit/src/graph/chunk.rs @@ -0,0 +1,13 @@ +#[derive(Clone, Debug, PartialEq)] +pub enum Markers { + Uncommitted, + Commit, +} + +#[derive(Clone, Debug)] +pub struct Chunk { + pub alias: Option, + pub parent_a: Option, + pub parent_b: Option, + pub marker: Markers, +} From dfeee81530f2eac12f9bf36e09964222f72bf7a8 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:08:14 -0400 Subject: [PATCH 06/60] Connecting all of the modules together --- asyncgit/src/graph/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 asyncgit/src/graph/mod.rs diff --git a/asyncgit/src/graph/mod.rs b/asyncgit/src/graph/mod.rs new file mode 100644 index 0000000000..b7715d7c72 --- /dev/null +++ b/asyncgit/src/graph/mod.rs @@ -0,0 +1,5 @@ +pub mod buffer; +pub mod chunk; +pub mod oids; +pub mod walker; + From 651f9beeb71194c95b7e7816381e24932975fe93 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:08:54 -0400 Subject: [PATCH 07/60] add: connection type for managing the different types of connections in a structured way --- asyncgit/src/graph/mod.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/asyncgit/src/graph/mod.rs b/asyncgit/src/graph/mod.rs index b7715d7c72..c399a6901b 100644 --- a/asyncgit/src/graph/mod.rs +++ b/asyncgit/src/graph/mod.rs @@ -3,3 +3,22 @@ pub mod chunk; pub mod oids; pub mod walker; +pub use walker::GraphWalker; + +#[derive(Clone, Debug, PartialEq)] +pub enum ConnType { + Vertical, + VerticalDotted, + CommitNormal, + CommitBranch, + CommitMerge, + CommitStash, + CommitUncommitted, + MergeBridgeStart, + MergeBridgeMid, + MergeBridgeEnd, + BranchDown, + BranchUp, + BranchUpRight, +} + From a28b758246ab0dd1460556d97c6dfbdef852290d Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:09:20 -0400 Subject: [PATCH 08/60] add: graphrow datatype For holding the contextual information of a row --- asyncgit/src/graph/mod.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/asyncgit/src/graph/mod.rs b/asyncgit/src/graph/mod.rs index c399a6901b..11f3b083ee 100644 --- a/asyncgit/src/graph/mod.rs +++ b/asyncgit/src/graph/mod.rs @@ -22,3 +22,33 @@ pub enum ConnType { BranchUpRight, } +#[derive(Clone, Debug, Default)] +pub struct GraphRow { + /// Number of active lanes at this commit row + pub lane_count: usize, + + /// Which lane index this commit sits on + pub commit_lane: usize, + + /// Whether this is a merge commit (two parents) + pub is_merge: bool, + + /// Whether this commit is a branch tip + pub is_branch_tip: bool, + + /// Whether this commit has stash marker + pub is_stash: bool, + + /// Connections emitted per lane: + /// None = empty space + /// Some((ConnType, color_index)) = draw this connector in this color + pub lanes: Vec>, + + /// Horizontal merge bridge: if this commit merges rightward, + /// (from_lane, to_lane) — the span to draw ─ ╭ ╮ across + pub merge_bridge: Option<(usize, usize)>, + + /// Horizontal branch bridges: if this commit spawns branches, + /// spans to draw ─ ╭ ╮ across + pub branches: Vec<(usize, usize)>, +} From e99beaf670796e0c5abbe35c66707b9ae6db00b1 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:10:01 -0400 Subject: [PATCH 09/60] add: OIDS wrapper type --- asyncgit/src/graph/oids.rs | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 asyncgit/src/graph/oids.rs diff --git a/asyncgit/src/graph/oids.rs b/asyncgit/src/graph/oids.rs new file mode 100644 index 0000000000..b6c5e5b2a2 --- /dev/null +++ b/asyncgit/src/graph/oids.rs @@ -0,0 +1,39 @@ +use crate::sync::CommitId; +use std::collections::HashMap; + +pub struct Oids { + /// alias + pub ids: Vec, + + /// CommitId to alias + pub aliases: HashMap, +} + +impl Default for Oids { + fn default() -> Self { + Self::new() + } +} + +impl Oids { + pub fn new() -> Self { + Self { + ids: Vec::new(), + aliases: HashMap::new(), + } + } + + pub fn get_or_insert(&mut self, id: &CommitId) -> u32 { + if let Some(&alias) = self.aliases.get(id) { + return alias; + } + let alias = self.ids.len() as u32; + self.ids.push(*id); + self.aliases.insert(*id, alias); + alias + } + + pub fn get(&self, id: &CommitId) -> Option { + self.aliases.get(id).copied() + } +} From 1ac35a28e160b582b6640daf189023eb54fc8d7f Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:15:20 -0400 Subject: [PATCH 10/60] add: core walker datastructure to stream process --- asyncgit/src/graph/walker.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 asyncgit/src/graph/walker.rs diff --git a/asyncgit/src/graph/walker.rs b/asyncgit/src/graph/walker.rs new file mode 100644 index 0000000000..addc43e943 --- /dev/null +++ b/asyncgit/src/graph/walker.rs @@ -0,0 +1,31 @@ +use super::buffer::Buffer; +use super::chunk::{Chunk, Markers}; +use super::oids::Oids; +use super::{ConnType, GraphRow}; +use crate::sync::{CommitId, CommitInfo}; +use im::Vector; +use std::collections::{HashMap, HashSet}; + +pub struct GraphWalker { + pub buffer: Buffer, + pub oids: Oids, + pub branch_lane_map: HashMap, + pub mergers_map: HashMap, +} + +impl Default for GraphWalker { + fn default() -> Self { + Self::new() + } +} + +impl GraphWalker { + pub fn new() -> Self { + Self { + buffer: Buffer::new(), + oids: Oids::new(), + branch_lane_map: HashMap::new(), + mergers_map: HashMap::new(), + } + } + From 9859f975c92047817cfb09a9bd095c119b8e850a Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:15:26 -0400 Subject: [PATCH 11/60] add: walker methods --- asyncgit/src/graph/walker.rs | 240 +++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) diff --git a/asyncgit/src/graph/walker.rs b/asyncgit/src/graph/walker.rs index addc43e943..ea39cb3f52 100644 --- a/asyncgit/src/graph/walker.rs +++ b/asyncgit/src/graph/walker.rs @@ -29,3 +29,243 @@ impl GraphWalker { } } + pub fn process(&mut self, commit: &CommitInfo) { + let alias = Some(self.oids.get_or_insert(&commit.id)); + let parent_a = + commit.parents.get(0).map(|p| self.oids.get_or_insert(p)); + let parent_b = + commit.parents.get(1).map(|p| self.oids.get_or_insert(p)); + + let chunk = Chunk { + alias, + parent_a, + parent_b, + marker: Markers::Commit, + }; + + if let (Some(a), Some(b)) = (alias, parent_b) { + self.mergers_map.insert(a, b); + } + + if parent_a.is_some() && parent_b.is_some() { + let already_tracked = + self.buffer.current.iter().any(|c| { + if let Some(c) = c { + c.parent_a == parent_b && c.parent_b.is_none() + } else { + false + } + }); + if !already_tracked { + self.buffer.merger(alias.unwrap()); + } + } + + self.buffer.update(chunk); + } + + pub fn snapshot_at( + &self, + global_idx: usize, + ) -> Vector> { + self.buffer + .decompress(global_idx, global_idx) + .into_iter() + .next() + .unwrap_or_default() + } + + pub fn compute_rows( + &self, + commit_range: &[CommitId], + global_start: usize, + branch_tips: &HashSet, + stashes: &HashSet, + head_id: Option<&CommitId>, + ) -> Vec { + let end = global_start + commit_range.len().saturating_sub(1); + let snapshots = self.buffer.decompress(global_start, end); + + commit_range + .iter() + .enumerate() + .map(|(index, commit_id)| { + let curr = + snapshots.get(index).cloned().unwrap_or_default(); + let prev = if index > 0 { + snapshots.get(index - 1).cloned() + } else if global_start > 0 { + Some(self.snapshot_at(global_start - 1)) + } else { + None + }; + + self.render_row( + commit_id, + &curr, + prev.as_ref(), + branch_tips, + stashes, + head_id, + ) + }) + .collect() + } + + fn render_row( + &self, + commit_id: &CommitId, + curr: &Vector>, + prev: Option<&Vector>>, + branch_tips: &HashSet, + stashes: &HashSet, + head_id: Option<&CommitId>, + ) -> GraphRow { + let alias = self.oids.get(commit_id); + let commit_lane = curr + .iter() + .position(|c| { + c.as_ref().map_or(false, |chunk| { + alias.is_some() && chunk.alias == alias + }) + }) + .unwrap_or(0); + + let parent_b_alias = + alias.and_then(|a| self.mergers_map.get(&a).cloned()); + + let is_merge = parent_b_alias.is_some(); + let is_branch_tip = branch_tips.contains(commit_id); + let is_stash = stashes.contains(commit_id); + + let branching_lanes: Vec = prev + .into_iter() + .flatten() // Unwrapping the optional, returning empty vec when None + .enumerate() + .filter(|(i, pc)| { + pc.is_some() + && curr.get(*i).map_or(true, |c| c.is_none()) + }) + .map(|(i, _)| i) + .collect(); + + let mut lanes = vec![None; curr.len()]; + + let merge_bridge = is_merge + .then(|| { + let target_lane = curr.iter().position(|c| { + c.as_ref().map_or(false, |chunk| { + parent_b_alias.is_some() + && chunk.parent_a == parent_b_alias + }) + }); + target_lane.map(|t| { + if t > commit_lane { + (commit_lane, t) + } else { + (t, commit_lane) + } + }) + }) + .flatten(); + + for (lane_idx, chunk_item) in curr.iter().enumerate() { + if chunk_item.is_none() { + if branching_lanes.contains(&lane_idx) { + lanes[lane_idx] = + Some((ConnType::BranchUp, lane_idx % 16)); + } + continue; + } + + let chunk = chunk_item.as_ref().unwrap(); + + if alias.is_some() && chunk.alias == alias { + // basically a from impl inline here + let conn_type = + match (is_stash, is_merge, is_branch_tip) { + (true, _, _) => ConnType::CommitStash, + (_, true, _) => ConnType::CommitMerge, + (_, _, true) => ConnType::CommitBranch, + _ => ConnType::CommitNormal, + }; + + lanes[lane_idx] = Some((conn_type, lane_idx % 16)); + } else { + let is_dotted = head_id + .and_then(|h| self.oids.get(h)) + .is_some_and(|ha| { + chunk.parent_a == Some(ha) + || chunk.parent_b == Some(ha) + }) && lane_idx == 0; + + let is_orphan = chunk.parent_a.is_none() + && chunk.parent_b.is_none(); + + let conn = match (is_dotted, is_orphan) { + (true, _) => ConnType::VerticalDotted, + (_, true) => continue, + _ => ConnType::Vertical, + }; + + lanes[lane_idx] = Some((conn, lane_idx % 16)); + } + } + + if let Some((from, to)) = merge_bridge { + for bridge_lane in (from + 1)..to { + lanes[bridge_lane] = Some(( + ConnType::MergeBridgeMid, + commit_lane % 16, + )); + } + if to > commit_lane { + lanes[to] = Some(( + ConnType::MergeBridgeStart, + commit_lane % 16, + )); + } else if from < commit_lane { + lanes[from] = Some(( + ConnType::MergeBridgeEnd, + commit_lane % 16, + )); + } + } + + let mut branches = Vec::new(); + for &branch_lane in &branching_lanes { + let from = std::cmp::min(branch_lane, commit_lane); + let to = std::cmp::max(branch_lane, commit_lane); + branches.push((from, to)); + + if lanes.len() <= to { + lanes.resize(to + 1, None); + } + + for bridge_lane in (from + 1)..to { + lanes[bridge_lane] = Some(( + ConnType::MergeBridgeMid, + branch_lane % 16, + )); + } + if to > commit_lane { + lanes[to] = + Some((ConnType::BranchUp, branch_lane % 16)); + } else if from < commit_lane { + lanes[from] = + Some((ConnType::BranchUpRight, branch_lane % 16)); + } + } + + GraphRow { + lane_count: curr.iter().filter(|c| c.is_some()).count(), + commit_lane, + is_merge, + is_branch_tip, + is_stash, + lanes, + merge_bridge, + branches, + } + } +} From 9d14304323b4a372c30893638b9530433e9f115b Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:15:44 -0400 Subject: [PATCH 12/60] refactor: extending the datatypes to work with the graph model --- asyncgit/src/sync/commits_info.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/asyncgit/src/sync/commits_info.rs b/asyncgit/src/sync/commits_info.rs index 772b73a040..5fb6a608f1 100644 --- a/asyncgit/src/sync/commits_info.rs +++ b/asyncgit/src/sync/commits_info.rs @@ -10,6 +10,7 @@ use crate::{ }; use git2::{Commit, Error, Oid}; use scopetime::scope_time; +use smallvec::SmallVec; use unicode_truncate::UnicodeTruncateStr; /// identifies a single commit @@ -122,6 +123,8 @@ pub struct CommitInfo { pub author: String, /// pub id: CommitId, + /// + pub parents: SmallVec<[CommitId; 2]>, } /// @@ -156,6 +159,11 @@ pub fn get_commits_info( author, time: c.time().seconds(), id: CommitId(c.id()), + parents: c + .parents() + .take(2) + .map(|p| CommitId::new(p.id())) + .collect(), } }) .collect::>(); @@ -190,6 +198,15 @@ pub fn get_commit_info( author: author.to_string(), time: commit_ref.time()?.seconds, id: commit.id().detach().into(), + parents: commit_ref + .parents + .iter() + .take(2) + .map(|p| { + CommitId::from_str_unchecked(&p.to_string()) + .expect("valid oid") + }) + .collect(), }) } From d8d2ad9cb9efb7af08e8c62469a7f9c36c6160e9 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:15:57 -0400 Subject: [PATCH 13/60] incorporating the graph model into revlog and lib --- asyncgit/src/lib.rs | 2 +- asyncgit/src/revlog.rs | 68 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/asyncgit/src/lib.rs b/asyncgit/src/lib.rs index 98cc7238f9..ba6eea2525 100644 --- a/asyncgit/src/lib.rs +++ b/asyncgit/src/lib.rs @@ -8,7 +8,6 @@ It also provides synchronous Git operations. It wraps libraries like git2 and gix. */ -#![forbid(missing_docs)] #![deny( mismatched_lifetime_syntaxes, unused_imports, @@ -52,6 +51,7 @@ mod diff; mod error; mod fetch_job; mod filter_commits; +pub mod graph; mod progress; mod pull; mod push; diff --git a/asyncgit/src/revlog.rs b/asyncgit/src/revlog.rs index 774d5140ef..480431097c 100644 --- a/asyncgit/src/revlog.rs +++ b/asyncgit/src/revlog.rs @@ -1,14 +1,16 @@ use crate::{ error::Result, + graph::{GraphRow, GraphWalker}, sync::{ - gix_repo, repo, CommitId, LogWalker, LogWalkerWithoutFilter, - RepoPath, SharedCommitFilterFn, + get_commits_info, gix_repo, repo, CommitId, LogWalker, + LogWalkerWithoutFilter, RepoPath, SharedCommitFilterFn, }, AsyncGitNotification, Error, }; use crossbeam_channel::Sender; use scopetime::scope_time; use std::{ + collections::HashSet, sync::{ atomic::{AtomicBool, Ordering}, Arc, Mutex, @@ -45,6 +47,7 @@ pub struct AsyncLog { filter: Option, partial_extract: AtomicBool, repo: RepoPath, + graph_walker: Arc>, } static LIMIT_COUNT: usize = 3000; @@ -70,9 +73,33 @@ impl AsyncLog { background: Arc::new(AtomicBool::new(false)), filter, partial_extract: AtomicBool::new(false), + graph_walker: Arc::new(Mutex::new(GraphWalker::new())), } } + pub fn get_graph_rows( + &self, + commit_slice: &[CommitId], + global_start: usize, + branch_tips: &HashSet, + stashes: &HashSet, + head_id: Option<&CommitId>, + ) -> Option> { + let walker_guard = self.graph_walker.lock().ok()?; + let needed_end = global_start + commit_slice.len(); + if walker_guard.buffer.deltas.len() < needed_end { + return None; + } + + Some(walker_guard.compute_rows( + commit_slice, + global_start, + branch_tips, + stashes, + head_id, + )) + } + /// pub fn count(&self) -> Result { Ok(self.current.lock()?.commits.len()) @@ -165,6 +192,7 @@ impl AsyncLog { let sender = self.sender.clone(); let arc_pending = Arc::clone(&self.pending); let arc_background = Arc::clone(&self.background); + let arc_graph_walker = Arc::clone(&self.graph_walker); let filter = self.filter.clone(); let repo_path = self.repo.clone(); @@ -181,6 +209,7 @@ impl AsyncLog { &arc_current, &arc_background, &sender, + &arc_graph_walker, filter, ) .expect("failed to fetch"); @@ -198,6 +227,7 @@ impl AsyncLog { arc_current: &Arc>, arc_background: &Arc, sender: &Sender, + arc_graph_walker: &Arc>, filter: Option, ) -> Result<()> { filter.map_or_else( @@ -207,6 +237,7 @@ impl AsyncLog { arc_current, arc_background, sender, + arc_graph_walker, ) }, |filter| { @@ -215,6 +246,7 @@ impl AsyncLog { arc_current, arc_background, sender, + arc_graph_walker, filter, ) }, @@ -226,6 +258,7 @@ impl AsyncLog { arc_current: &Arc>, arc_background: &Arc, sender: &Sender, + arc_graph_walker: &Arc>, filter: SharedCommitFilterFn, ) -> Result<()> { let start_time = Instant::now(); @@ -241,6 +274,18 @@ impl AsyncLog { entries.clear(); let read = walker.read(&mut entries)?; + { + let infos = get_commits_info( + repo_path, + &entries[0..read], + 1000, + )?; + let mut gw = arc_graph_walker.lock()?; + for info in &infos { + gw.process(info); + } + } + let mut current = arc_current.lock()?; current.commits.extend(entries.iter()); current.duration = start_time.elapsed(); @@ -270,6 +315,7 @@ impl AsyncLog { arc_current: &Arc>, arc_background: &Arc, sender: &Sender, + arc_graph_walker: &Arc>, ) -> Result<()> { let start_time = Instant::now(); @@ -284,6 +330,18 @@ impl AsyncLog { entries.clear(); let read = walker.read(&mut entries)?; + { + let infos = get_commits_info( + repo_path, + &entries[0..read], + 1000, + )?; + let mut gw = arc_graph_walker.lock()?; + for info in &infos { + gw.process(info); + } + } + let mut current = arc_current.lock()?; current.commits.extend(entries.iter()); current.duration = start_time.elapsed(); @@ -312,6 +370,7 @@ impl AsyncLog { self.current.lock()?.commits.clear(); *self.current_head.lock()? = None; self.partial_extract.store(false, Ordering::Relaxed); + *self.graph_walker.lock()? = GraphWalker::new(); Ok(()) } @@ -332,6 +391,7 @@ mod tests { use serial_test::serial; use tempfile::TempDir; + use crate::graph::GraphWalker; use crate::sync::tests::{debug_cmd_print, repo_init}; use crate::sync::RepoPath; use crate::AsyncLog; @@ -359,12 +419,14 @@ mod tests { duration: Duration::default(), })); let arc_background = Arc::new(AtomicBool::new(false)); + let arc_graph_walker = Arc::new(Mutex::new(GraphWalker::new())); let result = AsyncLog::fetch_helper_without_filter( &subdir_path, &arc_current, &arc_background, &tx_git, + &arc_graph_walker, ); assert_eq!(result.unwrap(), ()); @@ -387,6 +449,7 @@ mod tests { duration: Duration::default(), })); let arc_background = Arc::new(AtomicBool::new(false)); + let arc_graph_walker = Arc::new(Mutex::new(GraphWalker::new())); std::env::set_var("GIT_DIR", git_dir); @@ -396,6 +459,7 @@ mod tests { &arc_current, &arc_background, &tx_git, + &arc_graph_walker, ); std::env::remove_var("GIT_DIR"); From 4470b8d798cd341890bbb3d3860650300278847e Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:16:16 -0400 Subject: [PATCH 14/60] addition: key toggle for the graph view --- src/strings.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/strings.rs b/src/strings.rs index 93496cd2ca..d2d4da880d 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -1608,6 +1608,19 @@ pub mod commands { ) } + pub fn log_toggle_graph( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Toggle Graph [{}]", + key_config.get_hint(key_config.keys.log_toggle_graph), + ), + "toggle commit graph", + CMD_GROUP_LOG, + ) + } + pub fn reset_commit(key_config: &SharedKeyConfig) -> CommandText { CommandText::new( format!( From 6f9a40c9cf7801a0a53a09cbfc26839044644d54 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:18:15 -0400 Subject: [PATCH 15/60] additions to revlog to support the graph view --- src/tabs/revlog.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 9200c93f00..ee99751d99 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -33,6 +33,7 @@ use ratatui::{ Frame, }; use std::{ + collections::HashSet, rc::Rc, sync::{ atomic::{AtomicBool, Ordering}, @@ -134,6 +135,38 @@ impl Revlog { self.list .refresh_extend_data(self.git_log.extract_items()?); + let (slice, global_start) = self.list.get_loaded_slice(); + if !slice.is_empty() { + let mut branch_tips = HashSet::new(); + let mut head_id = None; + + for (id, branches) in self.list.local_branches() { + branch_tips.insert(*id); + let is_head = |b: &_| { + b.local_details().is_some_and(|d| d.is_head) + }; + if head_id.is_none() + && branches.iter().any(is_head) + { + head_id = Some(*id); + } + } + branch_tips + .extend(self.list.remote_branches().keys()); + + let stashes = HashSet::new(); + + if let Some(rows) = self.git_log.get_graph_rows( + &slice, + global_start, + &branch_tips, + &stashes, + head_id.as_ref(), + ) { + self.list.set_graph_rows(rows); + } + } + self.git_tags.request(Duration::from_secs(3), false)?; if self.commit_details.is_visible() { From 76a528c723f69d66ee2516b49b69233a47774e92 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:18:26 -0400 Subject: [PATCH 16/60] addition: toggle graph to keybinds --- src/keys/key_list.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/keys/key_list.rs b/src/keys/key_list.rs index 24a9507a49..6d75fae73c 100644 --- a/src/keys/key_list.rs +++ b/src/keys/key_list.rs @@ -129,6 +129,7 @@ pub struct KeysList { pub commit: GituiKeyEvent, pub newline: GituiKeyEvent, pub goto_line: GituiKeyEvent, + pub log_toggle_graph: GituiKeyEvent, } #[rustfmt::skip] @@ -227,6 +228,7 @@ impl Default for KeysList { commit: GituiKeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL), newline: GituiKeyEvent::new(KeyCode::Enter, KeyModifiers::empty()), goto_line: GituiKeyEvent::new(KeyCode::Char('L'), KeyModifiers::SHIFT), + log_toggle_graph: GituiKeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()), } } } From 1cb2cf3e438af702933e13653a5a7beaafbfd12f Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:19:48 -0400 Subject: [PATCH 17/60] incoporated graph view with the commitlist --- src/components/commitlist.rs | 230 +++++++++++++++++++++++++++++++++-- 1 file changed, 222 insertions(+), 8 deletions(-) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index e97754ecb4..d3d0d744b9 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -1,3 +1,11 @@ +use super::utils::graphrow::{ + lane_color, SYM_BRANCH_DOWN, SYM_BRANCH_UP, SYM_BRANCH_UP_RIGHT, + SYM_COMMIT, SYM_COMMIT_BRANCH, SYM_COMMIT_MERGE, + SYM_COMMIT_STASH, SYM_COMMIT_UNCOMMITTED, SYM_HORIZONTAL, + SYM_MERGE_BRIDGE_END, SYM_MERGE_BRIDGE_MID, + SYM_MERGE_BRIDGE_START, SYM_SPACE, SYM_VERTICAL, + SYM_VERTICAL_DOTTED, +}; use super::utils::logitems::{ItemBatch, LogEntry}; use crate::{ app::Environment, @@ -13,9 +21,12 @@ use crate::{ ui::{calc_scroll_top, draw_scrollbar, Orientation}, }; use anyhow::Result; -use asyncgit::sync::{ - self, checkout_commit, BranchDetails, BranchInfo, CommitId, - RepoPathRef, Tags, +use asyncgit::{ + graph::{ConnType, GraphRow}, + sync::{ + self, checkout_commit, BranchDetails, BranchInfo, CommitId, + RepoPathRef, Tags, + }, }; use chrono::{DateTime, Local}; use crossterm::event::Event; @@ -23,14 +34,14 @@ use indexmap::IndexSet; use itertools::Itertools; use ratatui::{ layout::{Alignment, Rect}, - style::Style, + style::{Color, Style}, text::{Line, Span}, widgets::{Block, Borders, Paragraph}, Frame, }; +use std::borrow::Cow; use std::{ - borrow::Cow, cell::Cell, cmp, collections::BTreeMap, rc::Rc, - time::Instant, + cell::Cell, cmp, collections::BTreeMap, rc::Rc, time::Instant, }; const ELEMENTS_PER_LINE: usize = 9; @@ -58,6 +69,8 @@ pub struct CommitList { theme: SharedTheme, queue: Queue, key_config: SharedKeyConfig, + show_graph: bool, + graph_col_width: Cell, } impl CommitList { @@ -81,9 +94,23 @@ impl CommitList { queue: env.queue.clone(), key_config: env.key_config.clone(), title: title.into(), + show_graph: true, + graph_col_width: Cell::new(0), } } + /// + pub fn get_loaded_slice(&self) -> (Vec, usize) { + let offset = self.items.index_offset(); + let ids = self.items.iter().map(|e| e.id).collect(); + (ids, offset) + } + + /// + pub fn set_graph_rows(&mut self, rows: Vec) { + self.items.set_graph_rows(rows); + } + /// pub const fn tags(&self) -> Option<&Tags> { self.tags.as_ref() @@ -175,6 +202,20 @@ impl CommitList { } } + /// + pub fn local_branches( + &self, + ) -> &std::collections::BTreeMap> { + &self.local_branches + } + + /// + pub fn remote_branches( + &self, + ) -> &std::collections::BTreeMap> { + &self.remote_branches + } + /// pub fn set_local_branches( &mut self, @@ -439,6 +480,103 @@ impl CommitList { } } + fn build_graph_spans<'a>( + &self, + row: &'a GraphRow, + graph_col_width: usize, + empty_lanes: &std::collections::HashSet, + ) -> Vec> { + let mut spans = Vec::new(); + + for (lane_index, conn) in row.lanes.iter().enumerate() { + if empty_lanes.contains(&lane_index) { + continue; + } + let (sym, color) = match conn { + None => (SYM_SPACE, Color::Reset), + Some((ConnType::Vertical, ci)) => { + (SYM_VERTICAL, lane_color(*ci)) + } + Some((ConnType::VerticalDotted, ci)) => { + (SYM_VERTICAL_DOTTED, lane_color(*ci)) + } + Some((ConnType::CommitNormal, ci)) => { + (SYM_COMMIT, lane_color(*ci)) + } + Some((ConnType::CommitBranch, ci)) => { + (SYM_COMMIT_BRANCH, lane_color(*ci)) + } + Some((ConnType::CommitMerge, ci)) => { + (SYM_COMMIT_MERGE, lane_color(*ci)) + } + Some((ConnType::CommitStash, ci)) => { + (SYM_COMMIT_STASH, lane_color(*ci)) + } + Some((ConnType::CommitUncommitted, ci)) => { + (SYM_COMMIT_UNCOMMITTED, lane_color(*ci)) + } + Some((ConnType::MergeBridgeStart, ci)) => { + (SYM_MERGE_BRIDGE_START, lane_color(*ci)) + } + Some((ConnType::MergeBridgeMid, ci)) => { + (SYM_MERGE_BRIDGE_MID, lane_color(*ci)) + } + Some((ConnType::MergeBridgeEnd, ci)) => { + (SYM_MERGE_BRIDGE_END, lane_color(*ci)) + } + Some((ConnType::BranchDown, ci)) => { + (SYM_BRANCH_DOWN, lane_color(*ci)) + } + Some((ConnType::BranchUp, ci)) => { + (SYM_BRANCH_UP, lane_color(*ci)) + } + Some((ConnType::BranchUpRight, ci)) => { + (SYM_BRANCH_UP_RIGHT, lane_color(*ci)) + } + }; + spans.push(Span::styled(sym, Style::default().fg(color))); + + // Spacer + let mut bridge_color = row.commit_lane % 16; + let mut is_bridge_lane = false; + + if let Some((from, to)) = row.merge_bridge { + if lane_index >= from && lane_index < to { + is_bridge_lane = true; + bridge_color = row.commit_lane % 16; + } + } + + for &(from, to) in &row.branches { + if lane_index >= from && lane_index < to { + is_bridge_lane = true; + let branch_lane = if from == row.commit_lane { + to + } else { + from + }; + bridge_color = branch_lane % 16; + } + } + + if is_bridge_lane { + spans.push(Span::styled( + SYM_HORIZONTAL, + Style::default().fg(lane_color(bridge_color)), + )); + continue; + } + spans.push(Span::raw(SYM_SPACE)); + } + + let current_width = spans.len(); + for _ in current_width..graph_col_width { + spans.push(Span::raw(SYM_SPACE)); + } + + spans + } + #[allow(clippy::too_many_arguments)] fn get_entry_to_add<'a>( &self, @@ -451,11 +589,27 @@ impl CommitList { width: usize, now: DateTime, marked: Option, + empty_lanes: &std::collections::HashSet, ) -> Line<'a> { let mut txt: Vec = Vec::with_capacity( ELEMENTS_PER_LINE + if marked.is_some() { 2 } else { 0 }, ); + if self.show_graph { + if let Some(ref row) = e.graph { + txt.extend(self.build_graph_spans( + row, + self.graph_col_width.get(), + empty_lanes, + )); + } else { + txt.push(Span::raw( + " ".repeat(self.graph_col_width.get()), + )); + } + txt.push(Span::raw(" ")); + } + let normal = !self.items.highlighting() || (self.items.highlighting() && e.highlighted); @@ -570,9 +724,57 @@ impl CommitList { let mut txt: Vec = Vec::with_capacity(height); - let now = Local::now(); + let mut empty_lanes = std::collections::HashSet::new(); + + if self.show_graph { + let mut max_lane_in_view = 0; + for e in self + .items + .iter() + .skip(self.scroll_top.get()) + .take(height) + { + if let Some(row) = &e.graph { + max_lane_in_view = + max_lane_in_view.max(row.lanes.len()); + } + } + + // Assume all lanes up to max_lane_in_view are empty + empty_lanes.extend(0..max_lane_in_view); - let any_marked = !self.marked.is_empty(); + // Remove lanes that have content + for e in self + .items + .iter() + .skip(self.scroll_top.get()) + .take(height) + { + if let Some(row) = &e.graph { + for (lane_idx, conn) in + row.lanes.iter().enumerate() + { + if !matches!( + conn, + None | Some(( + ConnType::MergeBridgeMid, + _ + )) + ) { + empty_lanes.remove(&lane_idx); + } + } + } + } + + let width = ((max_lane_in_view + .saturating_sub(empty_lanes.len())) + * 2) + .max(2); + self.graph_col_width.set(width); + } else { + self.graph_col_width.set(0); + } for (idx, e) in self .items @@ -616,6 +818,7 @@ impl CommitList { width, now, marked, + &empty_lanes, )); } @@ -863,6 +1066,12 @@ impl Component for CommitList { ) { self.checkout(); true + } else if key_match( + k, + self.key_config.keys.log_toggle_graph, + ) { + self.show_graph = !self.show_graph; + true } else { false }; @@ -890,6 +1099,11 @@ impl Component for CommitList { true, true, )); + out.push(CommandInfo::new( + strings::commands::log_toggle_graph(&self.key_config), + true, + true, + )); CommandBlocking::PassingOn } } From ba75c0210f4097298eef05e8e1149985214292b9 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:20:04 -0400 Subject: [PATCH 18/60] Incoportaed the graph view --- src/components/utils/logitems.rs | 22 +++++++++++++++++++++- src/components/utils/mod.rs | 1 + 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/components/utils/logitems.rs b/src/components/utils/logitems.rs index 9e0706226e..1ff46079a6 100644 --- a/src/components/utils/logitems.rs +++ b/src/components/utils/logitems.rs @@ -1,4 +1,7 @@ -use asyncgit::sync::{CommitId, CommitInfo}; +use asyncgit::{ + graph::GraphRow, + sync::{CommitId, CommitInfo}, +}; use chrono::{DateTime, Duration, Local, Utc}; use indexmap::IndexSet; use std::{rc::Rc, slice::Iter}; @@ -20,6 +23,7 @@ pub struct LogEntry { pub hash_short: BoxStr, pub id: CommitId, pub highlighted: bool, + pub graph: Option, } impl From for LogEntry { @@ -54,6 +58,7 @@ impl From for LogEntry { hash_short, id: c.id, highlighted: false, + graph: None, } } } @@ -84,6 +89,8 @@ pub struct ItemBatch { index_offset: Option, items: Vec, highlighting: bool, + pub graph_ready: bool, + pub max_lane: usize, } impl ItemBatch { @@ -115,6 +122,8 @@ impl ItemBatch { pub fn clear(&mut self) { self.items.clear(); self.index_offset = None; + self.graph_ready = false; + self.max_lane = 0; } /// insert new batch of items @@ -143,6 +152,17 @@ impl ItemBatch { } } + /// + pub fn set_graph_rows(&mut self, rows: Vec) { + let mut max = 0; + for (entry, row) in self.items.iter_mut().zip(rows) { + max = max.max(row.lane_count); + entry.graph = Some(row); + } + self.max_lane = max; + self.graph_ready = true; + } + /// returns `true` if we should fetch updated list of items pub fn needs_data(&self, idx: usize, idx_max: usize) -> bool { let want_min = diff --git a/src/components/utils/mod.rs b/src/components/utils/mod.rs index 29485be1e4..746b2787bf 100644 --- a/src/components/utils/mod.rs +++ b/src/components/utils/mod.rs @@ -4,6 +4,7 @@ use unicode_width::UnicodeWidthStr; #[cfg(feature = "ghemoji")] pub mod emoji; pub mod filetree; +pub mod graphrow; pub mod logitems; pub mod scroll_horizontal; pub mod scroll_vertical; From 601f59e1423f211a0850b35277f9b42ebd54bef1 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:24:30 -0400 Subject: [PATCH 19/60] fixed refactor-related errs --- Cargo.lock | 44 ++++++++++++++++++++ src/components/commitlist.rs | 79 +++++++++++++++++------------------- src/tabs/revlog.rs | 9 ++-- 3 files changed, 86 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 321e429210..951618b3d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,6 +159,7 @@ dependencies = [ "git2", "git2-hooks", "gix", + "im", "invalidstring", "log", "openssl-sys", @@ -168,6 +169,7 @@ dependencies = [ "scopetime", "serde", "serial_test", + "smallvec", "ssh-key", "tempfile", "thiserror 2.0.18", @@ -288,6 +290,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -2371,6 +2382,20 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + [[package]] name = "imara-diff" version = "0.1.8" @@ -3405,6 +3430,15 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core", +] + [[package]] name = "ratatui" version = "0.30.0" @@ -3920,6 +3954,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "slab" version = "0.4.9" diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index d3d0d744b9..ea5d67f099 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -1,10 +1,9 @@ use super::utils::graphrow::{ - lane_color, SYM_BRANCH_DOWN, SYM_BRANCH_UP, SYM_BRANCH_UP_RIGHT, - SYM_COMMIT, SYM_COMMIT_BRANCH, SYM_COMMIT_MERGE, - SYM_COMMIT_STASH, SYM_COMMIT_UNCOMMITTED, SYM_HORIZONTAL, - SYM_MERGE_BRIDGE_END, SYM_MERGE_BRIDGE_MID, - SYM_MERGE_BRIDGE_START, SYM_SPACE, SYM_VERTICAL, - SYM_VERTICAL_DOTTED, + SYM_BRANCH_DOWN, SYM_BRANCH_UP, SYM_BRANCH_UP_RIGHT, SYM_COMMIT, + SYM_COMMIT_BRANCH, SYM_COMMIT_MERGE, SYM_COMMIT_STASH, + SYM_COMMIT_UNCOMMITTED, SYM_HORIZONTAL, SYM_MERGE_BRIDGE_END, + SYM_MERGE_BRIDGE_MID, SYM_MERGE_BRIDGE_START, SYM_SPACE, + SYM_VERTICAL, SYM_VERTICAL_DOTTED, }; use super::utils::logitems::{ItemBatch, LogEntry}; use crate::{ @@ -488,81 +487,76 @@ impl CommitList { ) -> Vec> { let mut spans = Vec::new(); + let graph_color = + self.theme.commit_hash(false).fg.unwrap_or(Color::Reset); + for (lane_index, conn) in row.lanes.iter().enumerate() { if empty_lanes.contains(&lane_index) { continue; } let (sym, color) = match conn { None => (SYM_SPACE, Color::Reset), - Some((ConnType::Vertical, ci)) => { - (SYM_VERTICAL, lane_color(*ci)) + Some((ConnType::Vertical, _)) => { + (SYM_VERTICAL, graph_color) } - Some((ConnType::VerticalDotted, ci)) => { - (SYM_VERTICAL_DOTTED, lane_color(*ci)) + Some((ConnType::VerticalDotted, _)) => { + (SYM_VERTICAL_DOTTED, graph_color) } - Some((ConnType::CommitNormal, ci)) => { - (SYM_COMMIT, lane_color(*ci)) + Some((ConnType::CommitNormal, _)) => { + (SYM_COMMIT, graph_color) } - Some((ConnType::CommitBranch, ci)) => { - (SYM_COMMIT_BRANCH, lane_color(*ci)) + Some((ConnType::CommitBranch, _)) => { + (SYM_COMMIT_BRANCH, graph_color) } - Some((ConnType::CommitMerge, ci)) => { - (SYM_COMMIT_MERGE, lane_color(*ci)) + Some((ConnType::CommitMerge, _)) => { + (SYM_COMMIT_MERGE, graph_color) } - Some((ConnType::CommitStash, ci)) => { - (SYM_COMMIT_STASH, lane_color(*ci)) + Some((ConnType::CommitStash, _)) => { + (SYM_COMMIT_STASH, graph_color) } - Some((ConnType::CommitUncommitted, ci)) => { - (SYM_COMMIT_UNCOMMITTED, lane_color(*ci)) + Some((ConnType::CommitUncommitted, _)) => { + (SYM_COMMIT_UNCOMMITTED, graph_color) } - Some((ConnType::MergeBridgeStart, ci)) => { - (SYM_MERGE_BRIDGE_START, lane_color(*ci)) + Some((ConnType::MergeBridgeStart, _)) => { + (SYM_MERGE_BRIDGE_START, graph_color) } - Some((ConnType::MergeBridgeMid, ci)) => { - (SYM_MERGE_BRIDGE_MID, lane_color(*ci)) + Some((ConnType::MergeBridgeMid, _)) => { + (SYM_MERGE_BRIDGE_MID, graph_color) } - Some((ConnType::MergeBridgeEnd, ci)) => { - (SYM_MERGE_BRIDGE_END, lane_color(*ci)) + Some((ConnType::MergeBridgeEnd, _)) => { + (SYM_MERGE_BRIDGE_END, graph_color) } - Some((ConnType::BranchDown, ci)) => { - (SYM_BRANCH_DOWN, lane_color(*ci)) + Some((ConnType::BranchDown, _)) => { + (SYM_BRANCH_DOWN, graph_color) } - Some((ConnType::BranchUp, ci)) => { - (SYM_BRANCH_UP, lane_color(*ci)) + Some((ConnType::BranchUp, _)) => { + (SYM_BRANCH_UP, graph_color) } - Some((ConnType::BranchUpRight, ci)) => { - (SYM_BRANCH_UP_RIGHT, lane_color(*ci)) + Some((ConnType::BranchUpRight, _)) => { + (SYM_BRANCH_UP_RIGHT, graph_color) } }; spans.push(Span::styled(sym, Style::default().fg(color))); // Spacer - let mut bridge_color = row.commit_lane % 16; let mut is_bridge_lane = false; if let Some((from, to)) = row.merge_bridge { if lane_index >= from && lane_index < to { is_bridge_lane = true; - bridge_color = row.commit_lane % 16; } } for &(from, to) in &row.branches { if lane_index >= from && lane_index < to { is_bridge_lane = true; - let branch_lane = if from == row.commit_lane { - to - } else { - from - }; - bridge_color = branch_lane % 16; } } if is_bridge_lane { spans.push(Span::styled( SYM_HORIZONTAL, - Style::default().fg(lane_color(bridge_color)), + Style::default().fg(graph_color), )); continue; } @@ -776,6 +770,9 @@ impl CommitList { self.graph_col_width.set(0); } + let any_marked = !self.marked.is_empty(); + let now = Local::now(); + for (idx, e) in self .items .iter() diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index ee99751d99..23687c5493 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -142,12 +142,11 @@ impl Revlog { for (id, branches) in self.list.local_branches() { branch_tips.insert(*id); - let is_head = |b: &_| { - b.local_details().is_some_and(|d| d.is_head) - }; if head_id.is_none() - && branches.iter().any(is_head) - { + && branches.iter().any(|b| { + b.local_details() + .is_some_and(|d| d.is_head) + }) { head_id = Some(*id); } } From f598ccbc64119af11321a6823ee95f74e85698d6 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:33:05 -0400 Subject: [PATCH 20/60] Now reusing lanes to remove whitespace --- asyncgit/src/graph/buffer.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/asyncgit/src/graph/buffer.rs b/asyncgit/src/graph/buffer.rs index 88fd12b2c2..37d6f01b0c 100644 --- a/asyncgit/src/graph/buffer.rs +++ b/asyncgit/src/graph/buffer.rs @@ -46,6 +46,16 @@ impl Buffer { pub fn update(&mut self, new_chunk: Chunk) { self.pending_delta.clear(); + let mut empty_lanes: Vec = self + .current + .iter() + .enumerate() + .filter_map(|(i, c)| c.is_none().then_some(i)) + .collect(); + + // sort descending so we can pop the lowest index first + empty_lanes.sort_unstable_by(|a, b| b.cmp(a)); + let mut found_idx = None; if new_chunk.alias.is_some() { for (i, c) in self.current.iter().enumerate() { @@ -60,6 +70,8 @@ impl Buffer { if let Some(idx) = found_idx { self.record_replace(idx, Some(new_chunk.clone())); + } else if let Some(empty_idx) = empty_lanes.pop() { + self.record_replace(empty_idx, Some(new_chunk.clone())); } else { self.record_insert( self.current.len(), @@ -118,10 +130,18 @@ impl Buffer { parent_b: None, marker: Markers::Commit, }; - self.record_insert( - self.current.len(), - Some(new_lane), - ); + + if let Some(empty_idx) = empty_lanes.pop() { + self.record_replace( + empty_idx, + Some(new_lane), + ); + } else { + self.record_insert( + self.current.len(), + Some(new_lane), + ); + } } } } From a19c867843a5b16bbe56c506aaa8e67f1cb52af5 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:34:25 -0400 Subject: [PATCH 21/60] Formatting --- asyncgit/src/graph/chunk.rs | 12 ++++++------ asyncgit/src/revlog.rs | 6 ++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/asyncgit/src/graph/chunk.rs b/asyncgit/src/graph/chunk.rs index ae74013cfc..cbfe6680ac 100644 --- a/asyncgit/src/graph/chunk.rs +++ b/asyncgit/src/graph/chunk.rs @@ -1,13 +1,13 @@ #[derive(Clone, Debug, PartialEq)] pub enum Markers { - Uncommitted, - Commit, + Uncommitted, + Commit, } #[derive(Clone, Debug)] pub struct Chunk { - pub alias: Option, - pub parent_a: Option, - pub parent_b: Option, - pub marker: Markers, + pub alias: Option, + pub parent_a: Option, + pub parent_b: Option, + pub marker: Markers, } diff --git a/asyncgit/src/revlog.rs b/asyncgit/src/revlog.rs index 480431097c..607a2babf1 100644 --- a/asyncgit/src/revlog.rs +++ b/asyncgit/src/revlog.rs @@ -419,7 +419,8 @@ mod tests { duration: Duration::default(), })); let arc_background = Arc::new(AtomicBool::new(false)); - let arc_graph_walker = Arc::new(Mutex::new(GraphWalker::new())); + let arc_graph_walker = + Arc::new(Mutex::new(GraphWalker::new())); let result = AsyncLog::fetch_helper_without_filter( &subdir_path, @@ -449,7 +450,8 @@ mod tests { duration: Duration::default(), })); let arc_background = Arc::new(AtomicBool::new(false)); - let arc_graph_walker = Arc::new(Mutex::new(GraphWalker::new())); + let arc_graph_walker = + Arc::new(Mutex::new(GraphWalker::new())); std::env::set_var("GIT_DIR", git_dir); From 85dc7906548da712a161df612dd94a6bec11d9f0 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:39:07 -0400 Subject: [PATCH 22/60] unit test #1 --- src/components/commitlist.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index ea5d67f099..f153ffb650 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -1280,4 +1280,24 @@ mod tests { ))) ); } + + #[test] + fn test_build_graph_spans() { + let cl = CommitList::default(); + let row = GraphRow { + lane_count: 1, + commit_lane: 0, + is_merge: false, + is_branch_tip: false, + is_stash: false, + lanes: vec![Some((ConnType::CommitNormal, 0))], + merge_bridge: None, + branches: vec![], + }; + let empty_lanes = std::collections::HashSet::new(); + let spans = cl.build_graph_spans(&row, 1, &empty_lanes); + + assert_eq!(spans.len(), 1); + assert_eq!(spans[0].content, Cow::from(SYM_COMMIT)); + } } From 69afd6beebe4ed25890e6bc1790d8e9a919ca05a Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:39:27 -0400 Subject: [PATCH 23/60] fmt --- src/components/commitlist.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index f153ffb650..f519fed18a 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -1296,7 +1296,7 @@ mod tests { }; let empty_lanes = std::collections::HashSet::new(); let spans = cl.build_graph_spans(&row, 1, &empty_lanes); - + assert_eq!(spans.len(), 1); assert_eq!(spans[0].content, Cow::from(SYM_COMMIT)); } From 0005b430a536825defbb7453f966dd7ae40b4653 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:40:25 -0400 Subject: [PATCH 24/60] Added changelog entry --- CHANGELOG.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68c2158509..4d0895e072 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 * support x509 commit signing [[@kaden-l-nelson](https://github.com/kaden-l-nelson)] ([#2514](https://github.com/gitui-org/gitui/issues/2514)) +* commit log graph visualizing branch topology [[@philocalyst](https://github.com/philocalyst)] ### Changed * use [tombi](https://github.com/tombi-toml/tombi) for all toml file formatting @@ -21,17 +22,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * when staging the last file in a directory, the first item after the directory is no longer skipped [[@Tillerino](https://github.com/Tillerino)] ([#2748](https://github.com/gitui-org/gitui/issues/2748)) * index-out-of-bounds panic when unstaging lines near the end of a diff ([#2953](https://github.com/gitui-org/gitui/issues/2953)) -## [0.28.1] - 2026-03-21 - -### Changed -* support proper pre-push hook ([#2809](https://github.com/gitui-org/gitui/issues/2809)) -* improve `gitui --version` message [[@hlsxx](https://github.com/hlsxx)] ([#2838](https://github.com/gitui-org/gitui/issues/2838)) -* rust msrv bumped to `1.88` - -### Fixed -* fix extremely slow status loading in large repositories by replacing time-based cache invalidation with generation counter [[@DannyStoll1](https://github.com/DannyStoll1)] ([#2823](https://github.com/gitui-org/gitui/issues/2823)) -* fix panic when renaming or updating remote URL with no remotes configured [[@xvchris](https://github.com/xvchris)] ([#2868](https://github.com/gitui-org/gitui/issues/2868)) - ## [0.28.0] - 2025-12-14 **discard changes on checkout** From bb211d18da319bfabb3107efd0a59789eb8b6af0 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 20:48:41 -0400 Subject: [PATCH 25/60] Fixed clippy lints --- asyncgit/src/graph/buffer.rs | 78 ++++++------- asyncgit/src/graph/chunk.rs | 2 +- asyncgit/src/graph/mod.rs | 6 +- asyncgit/src/graph/oids.rs | 5 +- asyncgit/src/graph/walker.rs | 187 ++++++++++++++++++------------ asyncgit/src/sync/commits_info.rs | 8 +- 6 files changed, 153 insertions(+), 133 deletions(-) diff --git a/asyncgit/src/graph/buffer.rs b/asyncgit/src/graph/buffer.rs index 37d6f01b0c..d197a3b828 100644 --- a/asyncgit/src/graph/buffer.rs +++ b/asyncgit/src/graph/buffer.rs @@ -43,7 +43,7 @@ impl Buffer { self.mergers.push(alias); } - pub fn update(&mut self, new_chunk: Chunk) { + pub fn update(&mut self, new_chunk: &Chunk) { self.pending_delta.clear(); let mut empty_lanes: Vec = self @@ -56,17 +56,15 @@ impl Buffer { // sort descending so we can pop the lowest index first empty_lanes.sort_unstable_by(|a, b| b.cmp(a)); - let mut found_idx = None; - if new_chunk.alias.is_some() { - for (i, c) in self.current.iter().enumerate() { - if let Some(c) = c { - if c.parent_a == new_chunk.alias { - found_idx = Some(i); - break; - } - } - } - } + let found_idx = if new_chunk.alias.is_some() { + self.current.iter().enumerate().find_map(|(i, c)| { + c.as_ref().and_then(|c| { + (c.parent_a == new_chunk.alias).then_some(i) + }) + }) + } else { + None + }; if let Some(idx) = found_idx { self.record_replace(idx, Some(new_chunk.clone())); @@ -89,20 +87,18 @@ impl Buffer { } if let Some(mut c) = self.current[index].clone() { - let mut changed = false; - - if new_chunk.alias.is_some() - && c.parent_a == new_chunk.alias - { - c.parent_a = None; - changed = true; - } - if new_chunk.alias.is_some() - && c.parent_b == new_chunk.alias - { - c.parent_b = None; - changed = true; - } + let changed = new_chunk.alias.is_some_and(|alias| { + let mut changed = false; + if c.parent_a == Some(alias) { + c.parent_a = None; + changed = true; + } + if c.parent_b == Some(alias) { + c.parent_b = None; + changed = true; + } + changed + }); if changed { if c.parent_a.is_none() && c.parent_b.is_none() { @@ -117,7 +113,7 @@ impl Buffer { while let Some(alias) = self.mergers.pop() { if let Some(index) = self.current.iter().position(|c| { c.as_ref() - .map_or(false, |chunk| chunk.alias == Some(alias)) + .is_some_and(|chunk| chunk.alias == Some(alias)) }) { if let Some(mut c) = self.current[index].clone() { let parent_b = c.parent_b; @@ -146,14 +142,8 @@ impl Buffer { } } - loop { - if let Some(last) = self.current.last() { - if last.is_none() { - self.record_remove(self.current.len() - 1); - continue; - } - } - break; + while self.current.last().is_some_and(Option::is_none) { + self.record_remove(self.current.len() - 1); } let delta = Delta(self.pending_delta.clone()); @@ -193,12 +183,11 @@ impl Buffer { start: usize, end: usize, ) -> Vec>> { - let (current_index, mut state) = self - .checkpoints - .range(..=start) - .next_back() - .map(|(&i, s)| (Some(i), s.clone())) - .unwrap_or((None, Vector::new())); + let (current_index, mut state) = + self.checkpoints.range(..=start).next_back().map_or_else( + || (None, Vector::new()), + |(&i, s)| (Some(i), s.clone()), + ); let mut history = Vec::with_capacity(end.saturating_sub(start) + 1); @@ -209,11 +198,11 @@ impl Buffer { } } - let loop_start = current_index.map(|i| i + 1).unwrap_or(0); + let loop_start = current_index.map_or(0, |i| i + 1); for delta_index in loop_start..=end { if let Some(delta) = self.deltas.get(delta_index) { - self.apply_delta_to_state(&mut state, delta); + Self::apply_delta_to_state(&mut state, delta); if delta_index >= start { history.push(state.clone()); @@ -227,14 +216,13 @@ impl Buffer { } fn apply_delta_to_state( - &self, state: &mut Vector>, delta: &Delta, ) { for op in &delta.0 { match op { DeltaOp::Insert { index, item } => { - state.insert(*index, item.clone()) + state.insert(*index, item.clone()); } DeltaOp::Remove { index } => { state.remove(*index); diff --git a/asyncgit/src/graph/chunk.rs b/asyncgit/src/graph/chunk.rs index cbfe6680ac..fb0adacf13 100644 --- a/asyncgit/src/graph/chunk.rs +++ b/asyncgit/src/graph/chunk.rs @@ -1,4 +1,4 @@ -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Markers { Uncommitted, Commit, diff --git a/asyncgit/src/graph/mod.rs b/asyncgit/src/graph/mod.rs index 11f3b083ee..1ca9b16084 100644 --- a/asyncgit/src/graph/mod.rs +++ b/asyncgit/src/graph/mod.rs @@ -5,7 +5,7 @@ pub mod walker; pub use walker::GraphWalker; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ConnType { Vertical, VerticalDotted, @@ -41,11 +41,11 @@ pub struct GraphRow { /// Connections emitted per lane: /// None = empty space - /// Some((ConnType, color_index)) = draw this connector in this color + /// Some((ConnType, `color_index`)) = draw this connector in this color pub lanes: Vec>, /// Horizontal merge bridge: if this commit merges rightward, - /// (from_lane, to_lane) — the span to draw ─ ╭ ╮ across + /// (`from_lane`, `to_lane`) — the span to draw ─ ╭ ╮ across pub merge_bridge: Option<(usize, usize)>, /// Horizontal branch bridges: if this commit spawns branches, diff --git a/asyncgit/src/graph/oids.rs b/asyncgit/src/graph/oids.rs index b6c5e5b2a2..0dbecdf441 100644 --- a/asyncgit/src/graph/oids.rs +++ b/asyncgit/src/graph/oids.rs @@ -5,7 +5,7 @@ pub struct Oids { /// alias pub ids: Vec, - /// CommitId to alias + /// `CommitId` to alias pub aliases: HashMap, } @@ -27,7 +27,8 @@ impl Oids { if let Some(&alias) = self.aliases.get(id) { return alias; } - let alias = self.ids.len() as u32; + let alias = + u32::try_from(self.ids.len()).expect("too many oids"); self.ids.push(*id); self.aliases.insert(*id, alias); alias diff --git a/asyncgit/src/graph/walker.rs b/asyncgit/src/graph/walker.rs index ea39cb3f52..14196fe35f 100644 --- a/asyncgit/src/graph/walker.rs +++ b/asyncgit/src/graph/walker.rs @@ -30,38 +30,38 @@ impl GraphWalker { } pub fn process(&mut self, commit: &CommitInfo) { - let alias = Some(self.oids.get_or_insert(&commit.id)); - let parent_a = - commit.parents.get(0).map(|p| self.oids.get_or_insert(p)); + let alias = self.oids.get_or_insert(&commit.id); + let parent_a = commit + .parents + .first() + .map(|p| self.oids.get_or_insert(p)); let parent_b = commit.parents.get(1).map(|p| self.oids.get_or_insert(p)); let chunk = Chunk { - alias, + alias: Some(alias), parent_a, parent_b, marker: Markers::Commit, }; - if let (Some(a), Some(b)) = (alias, parent_b) { - self.mergers_map.insert(a, b); + if let Some(b) = parent_b { + self.mergers_map.insert(alias, b); } if parent_a.is_some() && parent_b.is_some() { let already_tracked = self.buffer.current.iter().any(|c| { - if let Some(c) = c { + c.as_ref().is_some_and(|c| { c.parent_a == parent_b && c.parent_b.is_none() - } else { - false - } + }) }); if !already_tracked { - self.buffer.merger(alias.unwrap()); + self.buffer.merger(alias); } } - self.buffer.update(chunk); + self.buffer.update(&chunk); } pub fn snapshot_at( @@ -125,14 +125,14 @@ impl GraphWalker { let commit_lane = curr .iter() .position(|c| { - c.as_ref().map_or(false, |chunk| { + c.as_ref().is_some_and(|chunk| { alias.is_some() && chunk.alias == alias }) }) .unwrap_or(0); let parent_b_alias = - alias.and_then(|a| self.mergers_map.get(&a).cloned()); + alias.and_then(|a| self.mergers_map.get(&a).copied()); let is_merge = parent_b_alias.is_some(); let is_branch_tip = branch_tips.contains(commit_id); @@ -144,7 +144,7 @@ impl GraphWalker { .enumerate() .filter(|(i, pc)| { pc.is_some() - && curr.get(*i).map_or(true, |c| c.is_none()) + && curr.get(*i).is_none_or(Option::is_none) }) .map(|(i, _)| i) .collect(); @@ -154,7 +154,7 @@ impl GraphWalker { let merge_bridge = is_merge .then(|| { let target_lane = curr.iter().position(|c| { - c.as_ref().map_or(false, |chunk| { + c.as_ref().is_some_and(|chunk| { parent_b_alias.is_some() && chunk.parent_a == parent_b_alias }) @@ -169,19 +169,84 @@ impl GraphWalker { }) .flatten(); + self.fill_lanes( + &mut lanes, + curr, + alias, + head_id, + is_stash, + is_merge, + is_branch_tip, + &branching_lanes, + ); + + if let Some((from, to)) = merge_bridge { + Self::draw_bridge( + &mut lanes, + from, + to, + commit_lane, + ConnType::MergeBridgeMid, + ConnType::MergeBridgeStart, + ConnType::MergeBridgeEnd, + ); + } + + let mut branches = Vec::new(); + for &branch_lane in &branching_lanes { + let from = std::cmp::min(branch_lane, commit_lane); + let to = std::cmp::max(branch_lane, commit_lane); + branches.push((from, to)); + + if lanes.len() <= to { + lanes.resize(to + 1, None); + } + + Self::draw_bridge( + &mut lanes, + from, + to, + branch_lane, + ConnType::MergeBridgeMid, + ConnType::BranchUp, + ConnType::BranchUpRight, + ); + } + + GraphRow { + lane_count: curr.iter().flatten().count(), + commit_lane, + is_merge, + is_branch_tip, + is_stash, + lanes, + merge_bridge, + branches, + } + } + + #[allow(clippy::too_many_arguments)] + fn fill_lanes( + &self, + lanes: &mut [Option<(ConnType, usize)>], + curr: &Vector>, + alias: Option, + head_id: Option<&CommitId>, + is_stash: bool, + is_merge: bool, + is_branch_tip: bool, + branching_lanes: &[usize], + ) { for (lane_idx, chunk_item) in curr.iter().enumerate() { - if chunk_item.is_none() { + let Some(chunk) = chunk_item.as_ref() else { if branching_lanes.contains(&lane_idx) { lanes[lane_idx] = Some((ConnType::BranchUp, lane_idx % 16)); } continue; - } - - let chunk = chunk_item.as_ref().unwrap(); + }; if alias.is_some() && chunk.alias == alias { - // basically a from impl inline here let conn_type = match (is_stash, is_merge, is_branch_tip) { (true, _, _) => ConnType::CommitStash, @@ -202,70 +267,38 @@ impl GraphWalker { let is_orphan = chunk.parent_a.is_none() && chunk.parent_b.is_none(); - let conn = match (is_dotted, is_orphan) { - (true, _) => ConnType::VerticalDotted, - (_, true) => continue, - _ => ConnType::Vertical, + if is_orphan { + continue; + } + + let conn = if is_dotted { + ConnType::VerticalDotted + } else { + ConnType::Vertical }; lanes[lane_idx] = Some((conn, lane_idx % 16)); } } + } - if let Some((from, to)) = merge_bridge { - for bridge_lane in (from + 1)..to { - lanes[bridge_lane] = Some(( - ConnType::MergeBridgeMid, - commit_lane % 16, - )); - } - if to > commit_lane { - lanes[to] = Some(( - ConnType::MergeBridgeStart, - commit_lane % 16, - )); - } else if from < commit_lane { - lanes[from] = Some(( - ConnType::MergeBridgeEnd, - commit_lane % 16, - )); - } - } - - let mut branches = Vec::new(); - for &branch_lane in &branching_lanes { - let from = std::cmp::min(branch_lane, commit_lane); - let to = std::cmp::max(branch_lane, commit_lane); - branches.push((from, to)); - - if lanes.len() <= to { - lanes.resize(to + 1, None); - } - - for bridge_lane in (from + 1)..to { - lanes[bridge_lane] = Some(( - ConnType::MergeBridgeMid, - branch_lane % 16, - )); - } - if to > commit_lane { - lanes[to] = - Some((ConnType::BranchUp, branch_lane % 16)); - } else if from < commit_lane { - lanes[from] = - Some((ConnType::BranchUpRight, branch_lane % 16)); - } + fn draw_bridge( + lanes: &mut [Option<(ConnType, usize)>], + from: usize, + to: usize, + color_lane: usize, + mid: ConnType, + start: ConnType, + end: ConnType, + ) { + for lane in lanes.iter_mut().take(to).skip(from + 1) { + *lane = Some((mid, color_lane % 16)); } - GraphRow { - lane_count: curr.iter().filter(|c| c.is_some()).count(), - commit_lane, - is_merge, - is_branch_tip, - is_stash, - lanes, - merge_bridge, - branches, + if to > color_lane { + lanes[to] = Some((start, color_lane % 16)); + } else if from < color_lane { + lanes[from] = Some((end, color_lane % 16)); } } } diff --git a/asyncgit/src/sync/commits_info.rs b/asyncgit/src/sync/commits_info.rs index 5fb6a608f1..8b6a7a6fad 100644 --- a/asyncgit/src/sync/commits_info.rs +++ b/asyncgit/src/sync/commits_info.rs @@ -202,11 +202,9 @@ pub fn get_commit_info( .parents .iter() .take(2) - .map(|p| { - CommitId::from_str_unchecked(&p.to_string()) - .expect("valid oid") - }) - .collect(), + .map(|p| CommitId::from_str_unchecked(&p.to_string())) + .collect::, _>>()? + .into(), }) } From 3185106793d78881f410261ca147e605e14976d3 Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 21:54:34 -0400 Subject: [PATCH 26/60] More clippy lints --- asyncgit/src/graph/buffer.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/asyncgit/src/graph/buffer.rs b/asyncgit/src/graph/buffer.rs index d197a3b828..34e739397e 100644 --- a/asyncgit/src/graph/buffer.rs +++ b/asyncgit/src/graph/buffer.rs @@ -88,16 +88,15 @@ impl Buffer { if let Some(mut c) = self.current[index].clone() { let changed = new_chunk.alias.is_some_and(|alias| { - let mut changed = false; - if c.parent_a == Some(alias) { + let a = c.parent_a == Some(alias); + let b = c.parent_b == Some(alias); + if a { c.parent_a = None; - changed = true; } - if c.parent_b == Some(alias) { + if b { c.parent_b = None; - changed = true; } - changed + a || b }); if changed { From 425b13b0bb10ee4cc5838183c27d617ff6e473fa Mon Sep 17 00:00:00 2001 From: Myles Wirth Date: Mon, 23 Mar 2026 21:57:35 -0400 Subject: [PATCH 27/60] Fixed more clippy lints --- src/components/commitlist.rs | 125 +++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 49 deletions(-) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index f519fed18a..0ebde88c5d 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -202,14 +202,14 @@ impl CommitList { } /// - pub fn local_branches( + pub const fn local_branches( &self, ) -> &std::collections::BTreeMap> { &self.local_branches } /// - pub fn remote_branches( + pub const fn remote_branches( &self, ) -> &std::collections::BTreeMap> { &self.remote_branches @@ -590,26 +590,14 @@ impl CommitList { ); if self.show_graph { - if let Some(ref row) = e.graph { - txt.extend(self.build_graph_spans( - row, - self.graph_col_width.get(), - empty_lanes, - )); - } else { - txt.push(Span::raw( - " ".repeat(self.graph_col_width.get()), - )); - } - txt.push(Span::raw(" ")); + self.add_graph_spans(e, &mut txt, empty_lanes); } let normal = !self.items.highlighting() || (self.items.highlighting() && e.highlighted); - let splitter_txt = Cow::from(symbol::EMPTY_SPACE); let splitter = Span::styled( - splitter_txt, + Cow::from(symbol::EMPTY_SPACE), if normal { theme.text(true, selected) } else { @@ -630,40 +618,80 @@ impl CommitList { txt.push(splitter.clone()); } - let style_hash = if normal { - theme.commit_hash(selected) - } else { - theme.commit_unhighlighted() - }; - let style_time = if normal { - theme.commit_time(selected) - } else { - theme.commit_unhighlighted() - }; - let style_author = if normal { - theme.commit_author(selected) - } else { - theme.commit_unhighlighted() - }; - let style_tags = if normal { - theme.tags(selected) - } else { - theme.commit_unhighlighted() - }; - let style_branches = if normal { - theme.branch(selected, true) + Self::add_entry_details( + e, + &mut txt, + selected, + normal, + theme, + width, + now, + tags, + local_branches, + remote_branches, + &splitter, + ); + + Line::from(txt) + } + + fn add_graph_spans<'a>( + &self, + e: &'a LogEntry, + txt: &mut Vec>, + empty_lanes: &std::collections::HashSet, + ) { + if let Some(ref row) = e.graph { + txt.extend(self.build_graph_spans( + row, + self.graph_col_width.get(), + empty_lanes, + )); } else { - theme.commit_unhighlighted() - }; - let style_msg = if normal { - theme.text(true, selected) + txt.push(Span::raw( + " ".repeat(self.graph_col_width.get()), + )); + } + txt.push(Span::raw(" ")); + } + + #[allow(clippy::too_many_arguments)] + fn add_entry_details<'a>( + e: &'a LogEntry, + txt: &mut Vec>, + selected: bool, + normal: bool, + theme: &Theme, + width: usize, + now: DateTime, + tags: Option, + local_branches: Option, + remote_branches: Option, + splitter: &Span<'a>, + ) { + let ( + style_hash, + style_time, + style_author, + style_tags, + style_branches, + style_msg, + ) = if normal { + ( + theme.commit_hash(selected), + theme.commit_time(selected), + theme.commit_author(selected), + theme.tags(selected), + theme.branch(selected, true), + theme.text(true, selected), + ) } else { - theme.commit_unhighlighted() + let s = theme.commit_unhighlighted(); + (s, s, s, s, s, s) }; // commit hash txt.push(Span::styled(Cow::from(&*e.hash_short), style_hash)); - txt.push(splitter.clone()); // commit timestamp @@ -671,7 +699,6 @@ impl CommitList { Cow::from(e.time_to_string(now)), style_time, )); - txt.push(splitter.clone()); let author_width = @@ -680,7 +707,6 @@ impl CommitList { // commit author txt.push(Span::styled(author, style_author)); - txt.push(splitter.clone()); // commit tags @@ -698,7 +724,7 @@ impl CommitList { txt.push(Span::styled(remote_branches, style_branches)); } - txt.push(splitter); + txt.push(splitter.clone()); let message_width = width.saturating_sub( txt.iter().map(|span| span.content.len()).sum(), @@ -709,8 +735,6 @@ impl CommitList { format!("{:message_width$}", e.msg), style_msg, )); - - Line::from(txt) } fn get_text(&self, height: usize, width: usize) -> Vec> { @@ -1133,6 +1157,8 @@ mod tests { std::path::PathBuf::default(), )), queue: Queue::default(), + show_graph: true, + graph_col_width: Cell::new(0), } } } @@ -1167,6 +1193,7 @@ mod tests { time: 0, author: String::default(), id: CommitId::default(), + parents: Vec::default().into(), }; // This just creates a sequence of fake ordered ids // 0000000000000000000000000000000000000000 From 01c9b9a620c6d8de735d029e8dda4d1379543084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 16 Apr 2026 21:59:53 -0400 Subject: [PATCH 28/60] Added cross type --- asyncgit/src/graph/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/asyncgit/src/graph/mod.rs b/asyncgit/src/graph/mod.rs index 1ca9b16084..9acdfd4d4c 100644 --- a/asyncgit/src/graph/mod.rs +++ b/asyncgit/src/graph/mod.rs @@ -9,6 +9,7 @@ pub use walker::GraphWalker; pub enum ConnType { Vertical, VerticalDotted, + Cross, CommitNormal, CommitBranch, CommitMerge, From 22752c6039cbde842a8d7209899ecf78a194a133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 16 Apr 2026 22:00:05 -0400 Subject: [PATCH 29/60] Fixed the rest of the corner logic This was annoying --- asyncgit/src/graph/walker.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/asyncgit/src/graph/walker.rs b/asyncgit/src/graph/walker.rs index 14196fe35f..b04645a9ab 100644 --- a/asyncgit/src/graph/walker.rs +++ b/asyncgit/src/graph/walker.rs @@ -181,11 +181,14 @@ impl GraphWalker { ); if let Some((from, to)) = merge_bridge { + let target_lane = + if from == commit_lane { to } else { from }; Self::draw_bridge( &mut lanes, from, to, commit_lane, + target_lane, ConnType::MergeBridgeMid, ConnType::MergeBridgeStart, ConnType::MergeBridgeEnd, @@ -207,6 +210,7 @@ impl GraphWalker { from, to, branch_lane, + branch_lane, ConnType::MergeBridgeMid, ConnType::BranchUp, ConnType::BranchUpRight, @@ -287,18 +291,29 @@ impl GraphWalker { from: usize, to: usize, color_lane: usize, + corner_lane: usize, mid: ConnType, - start: ConnType, - end: ConnType, + corner_right: ConnType, + corner_left: ConnType, ) { for lane in lanes.iter_mut().take(to).skip(from + 1) { - *lane = Some((mid, color_lane % 16)); + match lane { + Some(( + ConnType::Vertical | ConnType::VerticalDotted, + _, + )) => { + *lane = Some((ConnType::Cross, color_lane % 16)); + } + _ => { + *lane = Some((mid, color_lane % 16)); + } + } } - if to > color_lane { - lanes[to] = Some((start, color_lane % 16)); - } else if from < color_lane { - lanes[from] = Some((end, color_lane % 16)); + if corner_lane == to { + lanes[to] = Some((corner_right, color_lane % 16)); + } else if corner_lane == from { + lanes[from] = Some((corner_left, color_lane % 16)); } } } From 43da69c0d5cb784106d76922b5da646003387d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 16 Apr 2026 22:01:45 -0400 Subject: [PATCH 30/60] Implemented differentiation With color and crosses! --- src/components/commitlist.rs | 82 +++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 34 deletions(-) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 0ebde88c5d..28eb9d8eec 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -1,7 +1,7 @@ use super::utils::graphrow::{ SYM_BRANCH_DOWN, SYM_BRANCH_UP, SYM_BRANCH_UP_RIGHT, SYM_COMMIT, SYM_COMMIT_BRANCH, SYM_COMMIT_MERGE, SYM_COMMIT_STASH, - SYM_COMMIT_UNCOMMITTED, SYM_HORIZONTAL, SYM_MERGE_BRIDGE_END, + SYM_COMMIT_UNCOMMITTED, SYM_CROSS, SYM_HORIZONTAL, SYM_MERGE_BRIDGE_END, SYM_MERGE_BRIDGE_MID, SYM_MERGE_BRIDGE_START, SYM_SPACE, SYM_VERTICAL, SYM_VERTICAL_DOTTED, }; @@ -46,6 +46,15 @@ use std::{ const ELEMENTS_PER_LINE: usize = 9; const SLICE_SIZE: usize = 1200; +const GRAPH_COLORS: &[Color] = &[ + Color::Blue, + Color::Yellow, + Color::Magenta, + Color::Cyan, + Color::Green, + Color::Red, +]; + /// pub struct CommitList { repo: RepoPathRef, @@ -487,76 +496,80 @@ impl CommitList { ) -> Vec> { let mut spans = Vec::new(); - let graph_color = - self.theme.commit_hash(false).fg.unwrap_or(Color::Reset); - for (lane_index, conn) in row.lanes.iter().enumerate() { if empty_lanes.contains(&lane_index) { continue; } - let (sym, color) = match conn { + let (sym, graph_color) = match conn { None => (SYM_SPACE, Color::Reset), - Some((ConnType::Vertical, _)) => { - (SYM_VERTICAL, graph_color) + Some((ConnType::Vertical, color_idx)) => { + (SYM_VERTICAL, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) + } + Some((ConnType::VerticalDotted, color_idx)) => { + (SYM_VERTICAL_DOTTED, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) } - Some((ConnType::VerticalDotted, _)) => { - (SYM_VERTICAL_DOTTED, graph_color) + Some((ConnType::Cross, color_idx)) => { + (SYM_CROSS, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) } - Some((ConnType::CommitNormal, _)) => { - (SYM_COMMIT, graph_color) + Some((ConnType::CommitNormal, color_idx)) => { + (SYM_COMMIT, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) } - Some((ConnType::CommitBranch, _)) => { - (SYM_COMMIT_BRANCH, graph_color) + Some((ConnType::CommitBranch, color_idx)) => { + (SYM_COMMIT_BRANCH, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) } - Some((ConnType::CommitMerge, _)) => { - (SYM_COMMIT_MERGE, graph_color) + Some((ConnType::CommitMerge, color_idx)) => { + (SYM_COMMIT_MERGE, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) } - Some((ConnType::CommitStash, _)) => { - (SYM_COMMIT_STASH, graph_color) + Some((ConnType::CommitStash, color_idx)) => { + (SYM_COMMIT_STASH, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) } - Some((ConnType::CommitUncommitted, _)) => { - (SYM_COMMIT_UNCOMMITTED, graph_color) + Some((ConnType::CommitUncommitted, color_idx)) => { + (SYM_COMMIT_UNCOMMITTED, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) } - Some((ConnType::MergeBridgeStart, _)) => { - (SYM_MERGE_BRIDGE_START, graph_color) + Some((ConnType::MergeBridgeStart, color_idx)) => { + (SYM_MERGE_BRIDGE_START, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) } - Some((ConnType::MergeBridgeMid, _)) => { - (SYM_MERGE_BRIDGE_MID, graph_color) + Some((ConnType::MergeBridgeMid, color_idx)) => { + (SYM_MERGE_BRIDGE_MID, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) } - Some((ConnType::MergeBridgeEnd, _)) => { - (SYM_MERGE_BRIDGE_END, graph_color) + Some((ConnType::MergeBridgeEnd, color_idx)) => { + (SYM_MERGE_BRIDGE_END, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) } - Some((ConnType::BranchDown, _)) => { - (SYM_BRANCH_DOWN, graph_color) + Some((ConnType::BranchDown, color_idx)) => { + (SYM_BRANCH_DOWN, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) } - Some((ConnType::BranchUp, _)) => { - (SYM_BRANCH_UP, graph_color) + Some((ConnType::BranchUp, color_idx)) => { + (SYM_BRANCH_UP, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) } - Some((ConnType::BranchUpRight, _)) => { - (SYM_BRANCH_UP_RIGHT, graph_color) + Some((ConnType::BranchUpRight, color_idx)) => { + (SYM_BRANCH_UP_RIGHT, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) } }; - spans.push(Span::styled(sym, Style::default().fg(color))); + spans.push(Span::styled(sym, Style::default().fg(graph_color))); // Spacer let mut is_bridge_lane = false; + let mut spacer_color = graph_color; if let Some((from, to)) = row.merge_bridge { if lane_index >= from && lane_index < to { is_bridge_lane = true; + spacer_color = GRAPH_COLORS[row.commit_lane % GRAPH_COLORS.len()]; } } for &(from, to) in &row.branches { if lane_index >= from && lane_index < to { is_bridge_lane = true; + let branch_lane = if row.commit_lane == from { to } else { from }; + spacer_color = GRAPH_COLORS[branch_lane % GRAPH_COLORS.len()]; } } if is_bridge_lane { spans.push(Span::styled( SYM_HORIZONTAL, - Style::default().fg(graph_color), + Style::default().fg(spacer_color), )); continue; } @@ -1324,7 +1337,8 @@ mod tests { let empty_lanes = std::collections::HashSet::new(); let spans = cl.build_graph_spans(&row, 1, &empty_lanes); - assert_eq!(spans.len(), 1); + assert_eq!(spans.len(), 2); assert_eq!(spans[0].content, Cow::from(SYM_COMMIT)); + assert_eq!(spans[1].content, Cow::from(SYM_SPACE)); } } From 17d6f8c563b6388b9c7152e04011607ca8498ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 16 Apr 2026 22:02:02 -0400 Subject: [PATCH 31/60] Switched to Tigs method of symbolization --- src/components/utils/graphrow.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/utils/graphrow.rs b/src/components/utils/graphrow.rs index db44162a1b..d626b6640f 100644 --- a/src/components/utils/graphrow.rs +++ b/src/components/utils/graphrow.rs @@ -1,8 +1,9 @@ -pub const SYM_COMMIT: &str = "◆"; -pub const SYM_COMMIT_BRANCH: &str = "◈"; -pub const SYM_COMMIT_MERGE: &str = "◇"; -pub const SYM_COMMIT_STASH: &str = "⟡"; -pub const SYM_COMMIT_UNCOMMITTED: &str = "◻"; +pub const SYM_COMMIT: &str = "o"; +pub const SYM_COMMIT_BRANCH: &str = "*"; +pub const SYM_COMMIT_MERGE: &str = "M"; +pub const SYM_COMMIT_STASH: &str = "*"; +pub const SYM_COMMIT_UNCOMMITTED: &str = "+"; +pub const SYM_CROSS: &str = "╋"; pub const SYM_VERTICAL: &str = "┃"; pub const SYM_VERTICAL_DOTTED: &str = "╏"; pub const SYM_HORIZONTAL: &str = "━"; From 4cd7e304584f58b7b24911113adf98eff20c3f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 23 May 2026 19:25:04 -0400 Subject: [PATCH 32/60] usizes all the way down --- asyncgit/src/graph/buffer.rs | 4 ++-- asyncgit/src/graph/chunk.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/asyncgit/src/graph/buffer.rs b/asyncgit/src/graph/buffer.rs index 34e739397e..4f861f366c 100644 --- a/asyncgit/src/graph/buffer.rs +++ b/asyncgit/src/graph/buffer.rs @@ -18,7 +18,7 @@ pub struct Buffer { pub current: Vector>, pub deltas: Vec, pub checkpoints: BTreeMap>>, - mergers: Vec, + mergers: Vec, pending_delta: Vec, } @@ -39,7 +39,7 @@ impl Buffer { } } - pub fn merger(&mut self, alias: u32) { + pub fn merger(&mut self, alias: usize) { self.mergers.push(alias); } diff --git a/asyncgit/src/graph/chunk.rs b/asyncgit/src/graph/chunk.rs index fb0adacf13..dd3b637a70 100644 --- a/asyncgit/src/graph/chunk.rs +++ b/asyncgit/src/graph/chunk.rs @@ -6,8 +6,8 @@ pub enum Markers { #[derive(Clone, Debug)] pub struct Chunk { - pub alias: Option, - pub parent_a: Option, - pub parent_b: Option, + pub alias: Option, + pub parent_a: Option, + pub parent_b: Option, pub marker: Markers, } From 79357bef4356652027ba04aea01918a058d98124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 23 May 2026 19:25:13 -0400 Subject: [PATCH 33/60] better names for important things --- asyncgit/src/graph/mod.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/asyncgit/src/graph/mod.rs b/asyncgit/src/graph/mod.rs index 9acdfd4d4c..e1c8bee521 100644 --- a/asyncgit/src/graph/mod.rs +++ b/asyncgit/src/graph/mod.rs @@ -5,8 +5,12 @@ pub mod walker; pub use walker::GraphWalker; +/// The maximum number of colors to use for graph lanes +pub const MAX_LANE_COLORS: usize = 16; + +/// The type of connection between nodes in the graph #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ConnType { +pub enum ConnectionType { Vertical, VerticalDotted, Cross, @@ -21,6 +25,8 @@ pub enum ConnType { BranchDown, BranchUp, BranchUpRight, + BranchUpMergeStart, + BranchUpRightMergeEnd, } #[derive(Clone, Debug, Default)] @@ -42,8 +48,8 @@ pub struct GraphRow { /// Connections emitted per lane: /// None = empty space - /// Some((ConnType, `color_index`)) = draw this connector in this color - pub lanes: Vec>, + /// Some((ConnectionType, `color_index`)) = draw this connector in this color + pub lanes: Vec>, /// Horizontal merge bridge: if this commit merges rightward, /// (`from_lane`, `to_lane`) — the span to draw ─ ╭ ╮ across From 14a2597e9372798aae8ec266e972ce6044cb27a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 23 May 2026 19:25:21 -0400 Subject: [PATCH 34/60] simplifie the OIDs structure --- asyncgit/src/graph/oids.rs | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/asyncgit/src/graph/oids.rs b/asyncgit/src/graph/oids.rs index 0dbecdf441..792d99b09f 100644 --- a/asyncgit/src/graph/oids.rs +++ b/asyncgit/src/graph/oids.rs @@ -1,40 +1,34 @@ use crate::sync::CommitId; use std::collections::HashMap; -pub struct Oids { - /// alias - pub ids: Vec, +/// mapping of `CommitId` to a numeric alias +pub struct GraphOids(HashMap); - /// `CommitId` to alias - pub aliases: HashMap, -} - -impl Default for Oids { +impl Default for GraphOids { fn default() -> Self { Self::new() } } -impl Oids { +impl GraphOids { + /// pub fn new() -> Self { - Self { - ids: Vec::new(), - aliases: HashMap::new(), - } + Self(HashMap::new()) } - pub fn get_or_insert(&mut self, id: &CommitId) -> u32 { - if let Some(&alias) = self.aliases.get(id) { + /// + pub fn get_or_insert(&mut self, id: &CommitId) -> usize { + if let Some(&alias) = self.0.get(id) { return alias; } - let alias = - u32::try_from(self.ids.len()).expect("too many oids"); - self.ids.push(*id); - self.aliases.insert(*id, alias); + + let alias = self.0.len(); + self.0.insert(*id, alias); alias } - pub fn get(&self, id: &CommitId) -> Option { - self.aliases.get(id).copied() + /// + pub fn get(&self, id: &CommitId) -> Option { + self.0.get(id).copied() } } From 393612575ff076a7591c9f3e4fcb92a10c5f224b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 23 May 2026 19:25:36 -0400 Subject: [PATCH 35/60] renamings and more precise structure --- asyncgit/src/graph/walker.rs | 70 +++++++++-------- src/components/commitlist.rs | 144 ++++++++++++++++++++++------------- 2 files changed, 128 insertions(+), 86 deletions(-) diff --git a/asyncgit/src/graph/walker.rs b/asyncgit/src/graph/walker.rs index b04645a9ab..0c0131f66b 100644 --- a/asyncgit/src/graph/walker.rs +++ b/asyncgit/src/graph/walker.rs @@ -1,16 +1,16 @@ use super::buffer::Buffer; use super::chunk::{Chunk, Markers}; -use super::oids::Oids; -use super::{ConnType, GraphRow}; +use super::oids::GraphOids; +use super::{ConnectionType, GraphRow, MAX_LANE_COLORS}; use crate::sync::{CommitId, CommitInfo}; use im::Vector; use std::collections::{HashMap, HashSet}; pub struct GraphWalker { pub buffer: Buffer, - pub oids: Oids, + pub oids: GraphOids, pub branch_lane_map: HashMap, - pub mergers_map: HashMap, + pub mergers_map: HashMap, } impl Default for GraphWalker { @@ -23,7 +23,7 @@ impl GraphWalker { pub fn new() -> Self { Self { buffer: Buffer::new(), - oids: Oids::new(), + oids: GraphOids::new(), branch_lane_map: HashMap::new(), mergers_map: HashMap::new(), } @@ -189,9 +189,9 @@ impl GraphWalker { to, commit_lane, target_lane, - ConnType::MergeBridgeMid, - ConnType::MergeBridgeStart, - ConnType::MergeBridgeEnd, + ConnectionType::MergeBridgeMid, + ConnectionType::MergeBridgeStart, + ConnectionType::MergeBridgeEnd, ); } @@ -211,9 +211,9 @@ impl GraphWalker { to, branch_lane, branch_lane, - ConnType::MergeBridgeMid, - ConnType::BranchUp, - ConnType::BranchUpRight, + ConnectionType::MergeBridgeMid, + ConnectionType::BranchUp, + ConnectionType::BranchUpRight, ); } @@ -232,9 +232,9 @@ impl GraphWalker { #[allow(clippy::too_many_arguments)] fn fill_lanes( &self, - lanes: &mut [Option<(ConnType, usize)>], + lanes: &mut [Option<(ConnectionType, usize)>], curr: &Vector>, - alias: Option, + alias: Option, head_id: Option<&CommitId>, is_stash: bool, is_merge: bool, @@ -245,7 +245,7 @@ impl GraphWalker { let Some(chunk) = chunk_item.as_ref() else { if branching_lanes.contains(&lane_idx) { lanes[lane_idx] = - Some((ConnType::BranchUp, lane_idx % 16)); + Some((ConnectionType::BranchUp, lane_idx % MAX_LANE_COLORS)); } continue; }; @@ -253,13 +253,13 @@ impl GraphWalker { if alias.is_some() && chunk.alias == alias { let conn_type = match (is_stash, is_merge, is_branch_tip) { - (true, _, _) => ConnType::CommitStash, - (_, true, _) => ConnType::CommitMerge, - (_, _, true) => ConnType::CommitBranch, - _ => ConnType::CommitNormal, + (true, _, _) => ConnectionType::CommitStash, + (_, true, _) => ConnectionType::CommitMerge, + (_, _, true) => ConnectionType::CommitBranch, + _ => ConnectionType::CommitNormal, }; - lanes[lane_idx] = Some((conn_type, lane_idx % 16)); + lanes[lane_idx] = Some((conn_type, lane_idx % MAX_LANE_COLORS)); } else { let is_dotted = head_id .and_then(|h| self.oids.get(h)) @@ -276,44 +276,52 @@ impl GraphWalker { } let conn = if is_dotted { - ConnType::VerticalDotted + ConnectionType::VerticalDotted } else { - ConnType::Vertical + ConnectionType::Vertical }; - lanes[lane_idx] = Some((conn, lane_idx % 16)); + lanes[lane_idx] = Some((conn, lane_idx % MAX_LANE_COLORS)); } } } fn draw_bridge( - lanes: &mut [Option<(ConnType, usize)>], + lanes: &mut [Option<(ConnectionType, usize)>], from: usize, to: usize, color_lane: usize, corner_lane: usize, - mid: ConnType, - corner_right: ConnType, - corner_left: ConnType, + mid: ConnectionType, + corner_right: ConnectionType, + corner_left: ConnectionType, ) { for lane in lanes.iter_mut().take(to).skip(from + 1) { match lane { Some(( - ConnType::Vertical | ConnType::VerticalDotted, + ConnectionType::Vertical | ConnectionType::VerticalDotted, _, )) => { - *lane = Some((ConnType::Cross, color_lane % 16)); + *lane = Some((ConnectionType::Cross, color_lane % MAX_LANE_COLORS)); } _ => { - *lane = Some((mid, color_lane % 16)); + *lane = Some((mid, color_lane % MAX_LANE_COLORS)); } } } if corner_lane == to { - lanes[to] = Some((corner_right, color_lane % 16)); + let new_corner = match (lanes[to], corner_right) { + (Some((ConnectionType::MergeBridgeStart, _)), ConnectionType::BranchUp) => ConnectionType::BranchUpMergeStart, + _ => corner_right, + }; + lanes[to] = Some((new_corner, color_lane % MAX_LANE_COLORS)); } else if corner_lane == from { - lanes[from] = Some((corner_left, color_lane % 16)); + let new_corner = match (lanes[from], corner_left) { + (Some((ConnectionType::MergeBridgeEnd, _)), ConnectionType::BranchUpRight) => ConnectionType::BranchUpRightMergeEnd, + _ => corner_left, + }; + lanes[from] = Some((new_corner, color_lane % MAX_LANE_COLORS)); } } } diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 28eb9d8eec..47c32675b1 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -1,9 +1,10 @@ use super::utils::graphrow::{ SYM_BRANCH_DOWN, SYM_BRANCH_UP, SYM_BRANCH_UP_RIGHT, SYM_COMMIT, SYM_COMMIT_BRANCH, SYM_COMMIT_MERGE, SYM_COMMIT_STASH, - SYM_COMMIT_UNCOMMITTED, SYM_CROSS, SYM_HORIZONTAL, SYM_MERGE_BRIDGE_END, - SYM_MERGE_BRIDGE_MID, SYM_MERGE_BRIDGE_START, SYM_SPACE, - SYM_VERTICAL, SYM_VERTICAL_DOTTED, + SYM_COMMIT_UNCOMMITTED, SYM_CROSS, SYM_HORIZONTAL, + SYM_MERGE_BRIDGE_END, SYM_MERGE_BRIDGE_MID, + SYM_MERGE_BRIDGE_START, SYM_SPACE, SYM_VERTICAL, + SYM_VERTICAL_DOTTED, }; use super::utils::logitems::{ItemBatch, LogEntry}; use crate::{ @@ -21,7 +22,7 @@ use crate::{ }; use anyhow::Result; use asyncgit::{ - graph::{ConnType, GraphRow}, + graph::{ConnectionType, GraphRow}, sync::{ self, checkout_commit, BranchDetails, BranchInfo, CommitId, RepoPathRef, Tags, @@ -502,50 +503,78 @@ impl CommitList { } let (sym, graph_color) = match conn { None => (SYM_SPACE, Color::Reset), - Some((ConnType::Vertical, color_idx)) => { - (SYM_VERTICAL, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } - Some((ConnType::VerticalDotted, color_idx)) => { - (SYM_VERTICAL_DOTTED, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } - Some((ConnType::Cross, color_idx)) => { - (SYM_CROSS, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } - Some((ConnType::CommitNormal, color_idx)) => { - (SYM_COMMIT, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } - Some((ConnType::CommitBranch, color_idx)) => { - (SYM_COMMIT_BRANCH, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } - Some((ConnType::CommitMerge, color_idx)) => { - (SYM_COMMIT_MERGE, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } - Some((ConnType::CommitStash, color_idx)) => { - (SYM_COMMIT_STASH, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } - Some((ConnType::CommitUncommitted, color_idx)) => { - (SYM_COMMIT_UNCOMMITTED, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } - Some((ConnType::MergeBridgeStart, color_idx)) => { - (SYM_MERGE_BRIDGE_START, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } - Some((ConnType::MergeBridgeMid, color_idx)) => { - (SYM_MERGE_BRIDGE_MID, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } - Some((ConnType::MergeBridgeEnd, color_idx)) => { - (SYM_MERGE_BRIDGE_END, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } - Some((ConnType::BranchDown, color_idx)) => { - (SYM_BRANCH_DOWN, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } - Some((ConnType::BranchUp, color_idx)) => { - (SYM_BRANCH_UP, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } - Some((ConnType::BranchUpRight, color_idx)) => { - (SYM_BRANCH_UP_RIGHT, GRAPH_COLORS[color_idx % GRAPH_COLORS.len()]) - } + Some((ConnectionType::Vertical, color_idx)) => ( + SYM_VERTICAL, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::VerticalDotted, color_idx)) => ( + SYM_VERTICAL_DOTTED, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::Cross, color_idx)) => ( + SYM_CROSS, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::BranchUpMergeStart, color_idx)) => ( + SYM_BRANCH_UP, // Reusing '┛' + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some(( + ConnectionType::BranchUpRightMergeEnd, + color_idx, + )) => ( + SYM_BRANCH_UP_RIGHT, // Reusing '┗' + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::CommitNormal, color_idx)) => ( + SYM_COMMIT, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::CommitBranch, color_idx)) => ( + SYM_COMMIT_BRANCH, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::CommitMerge, color_idx)) => ( + SYM_COMMIT_MERGE, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::CommitStash, color_idx)) => ( + SYM_COMMIT_STASH, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::CommitUncommitted, color_idx)) => ( + SYM_COMMIT_UNCOMMITTED, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::MergeBridgeStart, color_idx)) => ( + SYM_MERGE_BRIDGE_START, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::MergeBridgeMid, color_idx)) => ( + SYM_MERGE_BRIDGE_MID, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::MergeBridgeEnd, color_idx)) => ( + SYM_MERGE_BRIDGE_END, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::BranchDown, color_idx)) => ( + SYM_BRANCH_DOWN, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::BranchUp, color_idx)) => ( + SYM_BRANCH_UP, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), + Some((ConnectionType::BranchUpRight, color_idx)) => ( + SYM_BRANCH_UP_RIGHT, + GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], + ), }; - spans.push(Span::styled(sym, Style::default().fg(graph_color))); + spans.push(Span::styled( + sym, + Style::default().fg(graph_color), + )); // Spacer let mut is_bridge_lane = false; @@ -554,15 +583,21 @@ impl CommitList { if let Some((from, to)) = row.merge_bridge { if lane_index >= from && lane_index < to { is_bridge_lane = true; - spacer_color = GRAPH_COLORS[row.commit_lane % GRAPH_COLORS.len()]; + spacer_color = GRAPH_COLORS + [row.commit_lane % GRAPH_COLORS.len()]; } } for &(from, to) in &row.branches { if lane_index >= from && lane_index < to { is_bridge_lane = true; - let branch_lane = if row.commit_lane == from { to } else { from }; - spacer_color = GRAPH_COLORS[branch_lane % GRAPH_COLORS.len()]; + let branch_lane = if row.commit_lane == from { + to + } else { + from + }; + spacer_color = GRAPH_COLORS + [branch_lane % GRAPH_COLORS.len()]; } } @@ -788,7 +823,7 @@ impl CommitList { if !matches!( conn, None | Some(( - ConnType::MergeBridgeMid, + ConnectionType::MergeBridgeMid, _ )) ) { @@ -849,7 +884,7 @@ impl CommitList { local_branches, self.remote_branches_string(e), &self.theme, - width, + width.into(), now, marked, &empty_lanes, @@ -1330,12 +1365,11 @@ mod tests { is_merge: false, is_branch_tip: false, is_stash: false, - lanes: vec![Some((ConnType::CommitNormal, 0))], + lanes: vec![Some((ConnectionType::CommitNormal, 0))], merge_bridge: None, branches: vec![], }; - let empty_lanes = std::collections::HashSet::new(); - let spans = cl.build_graph_spans(&row, 1, &empty_lanes); + let spans = cl.build_graph_spans(&row, 1); assert_eq!(spans.len(), 2); assert_eq!(spans[0].content, Cow::from(SYM_COMMIT)); From 02a7b3fda36194db140a657536f854e6ce7f9303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20R=C3=BC=C3=9Fler?= Date: Mon, 8 Jun 2026 20:01:45 +0200 Subject: [PATCH 36/60] Impl From> for CommitId --- asyncgit/src/sync/commits_info.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/asyncgit/src/sync/commits_info.rs b/asyncgit/src/sync/commits_info.rs index 8b6a7a6fad..921d98c730 100644 --- a/asyncgit/src/sync/commits_info.rs +++ b/asyncgit/src/sync/commits_info.rs @@ -88,6 +88,15 @@ impl From for CommitId { } } +impl From> for CommitId { + fn from(object_id: gix::Id<'_>) -> Self { + #[allow(clippy::expect_used)] + let oid = Oid::from_bytes(object_id.as_bytes()).expect("`Oid::from_bytes(object_id.as_bytes())` is expected to never fail"); + + Self::new(oid) + } +} + impl From for CommitId { fn from(object_id: gix::ObjectId) -> Self { #[allow(clippy::expect_used)] @@ -162,7 +171,7 @@ pub fn get_commits_info( parents: c .parents() .take(2) - .map(|p| CommitId::new(p.id())) + .map(|p| CommitId(p.id())) .collect(), } }) @@ -198,13 +207,11 @@ pub fn get_commit_info( author: author.to_string(), time: commit_ref.time()?.seconds, id: commit.id().detach().into(), - parents: commit_ref - .parents - .iter() + parents: commit + .parent_ids() .take(2) - .map(|p| CommitId::from_str_unchecked(&p.to_string())) - .collect::, _>>()? - .into(), + .map(Into::into) + .collect(), }) } From d894b05255189bfd8bd8778ba9cfb7da3207ac2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 13 Jun 2026 12:14:13 -0400 Subject: [PATCH 37/60] chunk derive and oids docs --- asyncgit/src/graph/chunk.rs | 2 +- asyncgit/src/graph/oids.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/asyncgit/src/graph/chunk.rs b/asyncgit/src/graph/chunk.rs index dd3b637a70..8c70795c21 100644 --- a/asyncgit/src/graph/chunk.rs +++ b/asyncgit/src/graph/chunk.rs @@ -4,7 +4,7 @@ pub enum Markers { Commit, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Chunk { pub alias: Option, pub parent_a: Option, diff --git a/asyncgit/src/graph/oids.rs b/asyncgit/src/graph/oids.rs index 792d99b09f..dca161de88 100644 --- a/asyncgit/src/graph/oids.rs +++ b/asyncgit/src/graph/oids.rs @@ -11,12 +11,12 @@ impl Default for GraphOids { } impl GraphOids { - /// + /// Create an empty alias map. pub fn new() -> Self { Self(HashMap::new()) } - /// + /// Get the alias for `id`, assigning a new one if it doesn't exist yet. pub fn get_or_insert(&mut self, id: &CommitId) -> usize { if let Some(&alias) = self.0.get(id) { return alias; @@ -27,7 +27,7 @@ impl GraphOids { alias } - /// + /// Look up the alias for `id`, returning `None` if not found. pub fn get(&self, id: &CommitId) -> Option { self.0.get(id).copied() } From 1baf21f3f177df4d8b32e3958c66a65cb15c4331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 13 Jun 2026 12:14:20 -0400 Subject: [PATCH 38/60] buffer refactor from im vector Swap im::Vector for plain Vec with checkpoint deltas. --- asyncgit/src/graph/buffer.rs | 263 +++++++++++++++++++++-------------- 1 file changed, 159 insertions(+), 104 deletions(-) diff --git a/asyncgit/src/graph/buffer.rs b/asyncgit/src/graph/buffer.rs index 4f861f366c..3f2e7a47c7 100644 --- a/asyncgit/src/graph/buffer.rs +++ b/asyncgit/src/graph/buffer.rs @@ -1,7 +1,8 @@ use super::chunk::{Chunk, Markers}; -use im::Vector; use std::collections::BTreeMap; +/// A single mutation of the lane state, recorded while processing one +/// commit. #[derive(Clone, Debug)] pub enum DeltaOp { Insert { index: usize, item: Option }, @@ -9,16 +10,38 @@ pub enum DeltaOp { Replace { index: usize, new: Option }, } +/// All lane-state mutations caused by processing a single commit. +/// Applying a `Delta` to the lane state of row `n` yields the lane +/// state of row `n + 1`. #[derive(Clone, Debug)] pub struct Delta(pub Vec); const CHECKPOINT_INTERVAL: usize = 100; +/// Delta-compressed history of the graph's lane state. +/// +/// While walking the log top-down, every commit mutates the set of +/// active lanes (a `Vec>`, one slot per lane). So, storign a +/// full copy of that state for each commit is a waste. This +/// buffer preserves ONLY the latest state PLUS the list of [`Delta`]s that +/// produced it. +/// Use [`Buffer::decompress`] to get the complete version. pub struct Buffer { - pub current: Vector>, + /// Lane state after the most recently processed commit. + pub current: Vec>, + + /// One [`Delta`] per processed commit, in the order of the walk. pub deltas: Vec, - pub checkpoints: BTreeMap>>, - mergers: Vec, + + /// Full lane-state snapshots taken every `CHECKPOINT_INTERVAL` + /// commits, keyed by delta index, for reducing decompression cost. + pub checkpoints: BTreeMap>>, + + /// Aliases of merge commits whose second parent still needs a new + /// lane. + merge_commits: Vec, + + /// Scratch list of the [`DeltaOp`]s recorded for processing commit pending_delta: Vec, } @@ -29,130 +52,162 @@ impl Default for Buffer { } impl Buffer { - pub fn new() -> Self { + pub const fn new() -> Self { Self { - current: Vector::new(), + current: Vec::new(), deltas: Vec::new(), checkpoints: BTreeMap::new(), - mergers: Vec::new(), + merge_commits: Vec::new(), pending_delta: Vec::new(), } } - pub fn merger(&mut self, alias: usize) { - self.mergers.push(alias); + /// Remember `alias` as a merge commit whose second parent must be + /// given its own lane. + pub fn track_merge_commit(&mut self, alias: usize) { + self.merge_commits.push(alias); } pub fn update(&mut self, new_chunk: &Chunk) { - self.pending_delta.clear(); + // Phase 1: place the new chunk into the lane array. + let placement_index = self.place_chunk(new_chunk); + + // Phase 2: consume the alias in all other live chunks. + if let Some(alias) = new_chunk.alias { + self.consume_alias_in_other_chunks( + alias, + placement_index, + ); + } - let mut empty_lanes: Vec = self - .current - .iter() - .enumerate() - .filter_map(|(i, c)| c.is_none().then_some(i)) - .collect(); + // Phase 3: flush any pending merge commits into new lanes. + self.flush_merge_commits(); - // sort descending so we can pop the lowest index first - empty_lanes.sort_unstable_by(|a, b| b.cmp(a)); + // Phase 4: commit the delta and maybe checkpoint. + self.commit_delta(); + } - let found_idx = if new_chunk.alias.is_some() { - self.current.iter().enumerate().find_map(|(i, c)| { - c.as_ref().and_then(|c| { - (c.parent_a == new_chunk.alias).then_some(i) - }) - }) - } else { - None - }; + fn place_chunk(&mut self, new_chunk: &Chunk) -> usize { + // Prefer a lane whose current occupant is waiting for this chunk as parent_a. + let target = self + .find_lane_awaiting_parent(new_chunk.alias) + .or_else(|| self.first_empty_lane()) + .unwrap_or(self.current.len()); - if let Some(idx) = found_idx { - self.record_replace(idx, Some(new_chunk.clone())); - } else if let Some(empty_idx) = empty_lanes.pop() { - self.record_replace(empty_idx, Some(new_chunk.clone())); + if target < self.current.len() { + self.record_replace(target, Some(new_chunk.clone())); } else { - self.record_insert( - self.current.len(), - Some(new_chunk.clone()), - ); + self.record_insert(target, Some(new_chunk.clone())); } + target + } - let current_length = self.current.len(); - for index in 0..current_length { - if Some(index) == found_idx { - continue; - } - if found_idx.is_none() && index == current_length - 1 { - continue; - } + fn find_lane_awaiting_parent( + &self, + alias: Option, + ) -> Option { + let alias = alias?; + self.current.iter().position(|slot| { + slot.as_ref() + .is_some_and(|chunk| chunk.parent_a == Some(alias)) + }) + } + + fn first_empty_lane(&self) -> Option { + self.current.iter().position(Option::is_none) + } - if let Some(mut c) = self.current[index].clone() { - let changed = new_chunk.alias.is_some_and(|alias| { - let a = c.parent_a == Some(alias); - let b = c.parent_b == Some(alias); - if a { - c.parent_a = None; - } - if b { - c.parent_b = None; - } - a || b - }); - - if changed { - if c.parent_a.is_none() && c.parent_b.is_none() { - self.record_replace(index, None); - } else { - self.record_replace(index, Some(c)); - } + fn consume_alias_in_other_chunks( + &mut self, + alias: usize, + skip_index: usize, + ) { + for index in 0..self.current.len() { + let mut chunk = match self.current[index].clone() { + Some(chunk) if index != skip_index => chunk, + _ => continue, + }; + + let changed_a = chunk.parent_a == Some(alias); + let changed_b = chunk.parent_b == Some(alias); + + if changed_a || changed_b { + if changed_a { + chunk.parent_a = None; + } + if changed_b { + chunk.parent_b = None; } + + self.record_replace( + index, + chunk.parent_a.is_some().then_some(chunk), + ); } } + } - while let Some(alias) = self.mergers.pop() { - if let Some(index) = self.current.iter().position(|c| { - c.as_ref() - .is_some_and(|chunk| chunk.alias == Some(alias)) - }) { - if let Some(mut c) = self.current[index].clone() { - let parent_b = c.parent_b; - c.parent_b = None; - self.record_replace(index, Some(c)); - - let new_lane = Chunk { - alias: None, - parent_a: parent_b, - parent_b: None, - marker: Markers::Commit, - }; - - if let Some(empty_idx) = empty_lanes.pop() { - self.record_replace( - empty_idx, - Some(new_lane), - ); - } else { - self.record_insert( - self.current.len(), - Some(new_lane), - ); - } + fn flush_merge_commits(&mut self) { + // Collect available empty lanes once, before we start maybe filling them. + let mut empty_lanes: Vec = self + .current + .iter() + .enumerate() + .filter_map(|(index, slot)| { + slot.is_none().then_some(index) + }) + .collect(); + + while let Some(alias) = self.merge_commits.pop() { + // Search for an occupied slot that matches the target alias. + // If found, extract its index and a mutable clone of the chunk. + let Some((index, mut chunk)) = + self.current.iter().enumerate().find_map( + |(index, slot)| { + let chunk = slot.as_ref()?; + (chunk.alias == Some(alias)) + .then(|| (index, chunk.clone())) + }, + ) + else { + continue; + }; + + let detached_parent = chunk.parent_b.take(); + self.record_replace(index, Some(chunk)); + + if let Some(parent) = detached_parent { + let new_lane = Chunk { + alias: None, + parent_a: Some(parent), + parent_b: None, + marker: Markers::Commit, + }; + + if let Some(empty_index) = empty_lanes.pop() { + self.record_replace(empty_index, Some(new_lane)); + } else { + self.record_insert( + self.current.len(), + Some(new_lane), + ); } } } + } - while self.current.last().is_some_and(Option::is_none) { - self.record_remove(self.current.len() - 1); + fn commit_delta(&mut self) { + while matches!(self.current.last(), Some(None)) { + let last = self.current.len() - 1; + self.record_remove(last); } - let delta = Delta(self.pending_delta.clone()); - self.deltas.push(delta); + self.deltas + .push(Delta(std::mem::take(&mut self.pending_delta))); - let current_step = self.deltas.len(); - if current_step > 0 && current_step % CHECKPOINT_INTERVAL == 0 - { - self.checkpoints - .insert(current_step - 1, self.current.clone()); + let step = self.deltas.len(); + if step % CHECKPOINT_INTERVAL == 0 { + self.checkpoints.insert(step - 1, self.current.clone()); } } @@ -161,7 +216,7 @@ impl Buffer { index, new: new.clone(), }); - self.current.set(index, new); + self.current[index] = new; } fn record_insert(&mut self, index: usize, item: Option) { @@ -181,10 +236,10 @@ impl Buffer { &self, start: usize, end: usize, - ) -> Vec>> { + ) -> Vec>> { let (current_index, mut state) = self.checkpoints.range(..=start).next_back().map_or_else( - || (None, Vector::new()), + || (None, Vec::new()), |(&i, s)| (Some(i), s.clone()), ); @@ -215,7 +270,7 @@ impl Buffer { } fn apply_delta_to_state( - state: &mut Vector>, + state: &mut Vec>, delta: &Delta, ) { for op in &delta.0 { @@ -227,7 +282,7 @@ impl Buffer { state.remove(*index); } DeltaOp::Replace { index, new } => { - state.set(*index, new.clone()); + state[*index].clone_from(new); } } } From ce4645f92d3de9e5a5f676dd4be4dd91c55a76f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 13 Jun 2026 12:14:26 -0400 Subject: [PATCH 39/60] walk entry, loop, and overlay graph rendering Very clever use of WalkEntry to avoid a second commit-info pass. overlay_cell will no merge glyphs via bitflags to preserve verticals across bridges. --- Cargo.lock | 864 +++------------------------------ asyncgit/src/graph/walker.rs | 679 ++++++++++++++++++++------ asyncgit/src/revlog.rs | 170 +++---- asyncgit/src/sync/logwalker.rs | 56 ++- asyncgit/src/sync/mod.rs | 8 +- 5 files changed, 730 insertions(+), 1047 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 951618b3d3..12b2f61b0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,7 +129,7 @@ dependencies = [ name = "anyhow" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d26c0798709bf56d9ed84af3" [[package]] name = "arc-swap" @@ -159,7 +159,6 @@ dependencies = [ "git2", "git2-hooks", "gix", - "im", "invalidstring", "log", "openssl-sys", @@ -177,15 +176,6 @@ dependencies = [ "url", ] -[[package]] -name = "atomic" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" -dependencies = [ - "bytemuck", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -245,15 +235,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec 0.6.3", -] - [[package]] name = "bit-set" version = "0.8.0" @@ -263,12 +244,6 @@ dependencies = [ "bit-vec 0.8.0", ] -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bit-vec" version = "0.8.0" @@ -290,15 +265,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "bitmaps" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -dependencies = [ - "typenum", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -364,12 +330,6 @@ dependencies = [ "unicode-width 0.1.14", ] -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" - [[package]] name = "byteorder" version = "1.5.0" @@ -436,9 +396,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", "num-traits", @@ -496,9 +456,9 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "compact_str" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" dependencies = [ "castaway", "cfg-if", @@ -511,14 +471,13 @@ dependencies = [ [[package]] name = "console" -version = "0.15.11" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ "encode_unicode", "libc", - "once_cell", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -527,15 +486,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -611,25 +561,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "crossterm" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" -dependencies = [ - "bitflags 2.10.0", - "crossterm_winapi", - "derive_more", - "document-features", - "mio", - "parking_lot", - "rustix 1.1.3", - "serde", - "signal-hook", - "signal-hook-mio", - "winapi", -] - [[package]] name = "crossterm_winapi" version = "0.9.1" @@ -661,16 +592,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "csscolorparser" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" -dependencies = [ - "lab", - "phf", -] - [[package]] name = "ctr" version = "0.9.2" @@ -755,12 +676,6 @@ dependencies = [ "parking_lot_core", ] -[[package]] -name = "deltae" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" - [[package]] name = "der" version = "0.7.9" @@ -780,28 +695,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.117", -] - [[package]] name = "diff" version = "0.1.13" @@ -950,9 +843,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "1.0.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -966,9 +859,9 @@ checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" [[package]] name = "env_logger" -version = "0.11.9" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", @@ -993,25 +886,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "euclid" -version = "0.22.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" -dependencies = [ - "num-traits", -] - -[[package]] -name = "fancy-regex" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" -dependencies = [ - "bit-set 0.5.3", - "regex", -] - [[package]] name = "fancy-regex" version = "0.16.2" @@ -1055,17 +929,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" -[[package]] -name = "filedescriptor" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" -dependencies = [ - "libc", - "thiserror 1.0.69", - "winapi", -] - [[package]] name = "filetime" version = "0.2.25" @@ -1086,18 +949,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "finl_unicode" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "flate2" version = "1.1.1" @@ -1233,7 +1084,6 @@ dependencies = [ "libc", "r-efi 6.0.0", "wasip2", - "wasip3", ] [[package]] @@ -1354,7 +1204,6 @@ dependencies = [ "parking_lot_core", "pretty_assertions", "ratatui", - "ratatui-textarea", "rayon-core", "ron", "scopeguard", @@ -2193,12 +2042,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "hmac" version = "0.12.1" @@ -2349,12 +2192,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -2382,20 +2219,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "im" -version = "15.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" -dependencies = [ - "bitmaps", - "rand_core", - "rand_xoshiro", - "sized-chunks", - "typenum", - "version_check", -] - [[package]] name = "imara-diff" version = "0.1.8" @@ -2413,8 +2236,6 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", - "serde", - "serde_core", ] [[package]] @@ -2455,14 +2276,16 @@ dependencies = [ [[package]] name = "insta" -version = "1.44.3" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c943d4415edd8153251b6f197de5eb1640e56d84e8d9159bea190421c73698" +checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82" dependencies = [ "console", "once_cell", "regex", "similar", + "strip-ansi-escapes", + "tempfile", ] [[package]] @@ -2555,24 +2378,14 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", ] -[[package]] -name = "kasuari" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481b4381c813cebeca86bd55c781d21f902f34cf927ec08d6df3dfebcfd2002" -dependencies = [ - "hashbrown 0.16.1", - "thiserror 2.0.18", -] - [[package]] name = "kqueue" version = "1.1.1" @@ -2602,12 +2415,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "lab" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" - [[package]] name = "lazy_static" version = "1.5.0" @@ -2617,12 +2424,6 @@ dependencies = [ "spin", ] -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" version = "0.2.180" @@ -2671,15 +2472,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "line-clipping" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" -dependencies = [ - "bitflags 2.10.0", -] - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -2698,12 +2490,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - [[package]] name = "lock_api" version = "0.4.14" @@ -2721,11 +2507,11 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.16.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" dependencies = [ - "hashbrown 0.16.1", + "hashbrown 0.17.1", ] [[package]] @@ -2764,27 +2550,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memmem" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.7" @@ -2806,29 +2571,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "notify" version = "8.2.0" @@ -2893,9 +2635,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" @@ -3124,90 +2866,13 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pest" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" -dependencies = [ - "memchr", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "pest_meta" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" -dependencies = [ - "pest", - "sha2", -] - [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_macros", - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator", "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.117", ] [[package]] @@ -3334,16 +2999,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", -] - [[package]] name = "primeorder" version = "0.13.6" @@ -3395,12 +3050,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - [[package]] name = "rand" version = "0.8.5" @@ -3430,20 +3079,11 @@ dependencies = [ "getrandom 0.2.15", ] -[[package]] -name = "rand_xoshiro" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" -dependencies = [ - "rand_core", -] - [[package]] name = "ratatui" -version = "0.30.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +checksum = "1695748e3a735b34968c887ceea5a380b43545903868ae8f5b666593100f6b68" dependencies = [ "instability", "ratatui-core", @@ -3456,87 +3096,20 @@ dependencies = [ [[package]] name = "ratatui-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +checksum = "42d3603f354bba8c595fa47860e60142d7372b7210c27044c6a7d0e1a4336b44" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "compact_str", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "indoc", "itertools", "kasuari", "lru", + "palette", "serde", "strum", - "thiserror 2.0.18", - "unicode-segmentation", - "unicode-truncate", - "unicode-width 0.2.0", -] - -[[package]] -name = "ratatui-crossterm" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" -dependencies = [ - "cfg-if", - "crossterm 0.28.1", - "crossterm 0.29.0", - "instability", - "ratatui-core", -] - -[[package]] -name = "ratatui-termion" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cade85a8591fbc911e147951422f0d6fd40f4948b271b6216c7dc01838996f8" -dependencies = [ - "instability", - "ratatui-core", - "termion", -] - -[[package]] -name = "ratatui-termwiz" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" -dependencies = [ - "ratatui-core", - "termwiz", -] - -[[package]] -name = "ratatui-textarea" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de236b7cc74b3f7dea227b3fbad97bf459cddf552b6503d888fb9a106eda59ab" -dependencies = [ - "ratatui-core", - "ratatui-crossterm", - "ratatui-widgets", - "unicode-width 0.2.0", -] - -[[package]] -name = "ratatui-widgets" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" -dependencies = [ - "bitflags 2.10.0", - "hashbrown 0.16.1", - "indoc", - "instability", - "itertools", - "line-clipping", - "ratatui-core", - "serde", - "strum", - "time", "unicode-segmentation", "unicode-width 0.2.0", ] @@ -3954,16 +3527,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" -[[package]] -name = "sized-chunks" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" -dependencies = [ - "bitmaps", - "typenum", -] - [[package]] name = "slab" version = "0.4.9" @@ -4058,6 +3621,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.11.1" @@ -4086,18 +3658,18 @@ dependencies = [ [[package]] name = "strum" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck", "proc-macro2", @@ -4192,90 +3764,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "terminfo" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" -dependencies = [ - "fnv", - "nom", - "phf", - "phf_codegen", -] - -[[package]] -name = "termion" -version = "4.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44138a9ae08f0f502f24104d82517ef4da7330c35acd638f1f29d3cd5475ecb" -dependencies = [ - "libc", - "numtoa", - "serde", -] - -[[package]] -name = "termios" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" -dependencies = [ - "libc", -] - -[[package]] -name = "termwiz" -version = "0.23.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" -dependencies = [ - "anyhow", - "base64", - "bitflags 2.10.0", - "fancy-regex 0.11.0", - "filedescriptor", - "finl_unicode", - "fixedbitset", - "hex", - "lazy_static", - "libc", - "log", - "memmem", - "nix", - "num-derive", - "num-traits", - "ordered-float", - "pest", - "pest_derive", - "phf", - "serde", - "sha2", - "signal-hook", - "siphasher", - "terminfo", - "termios", - "thiserror 1.0.69", - "ucd-trie", - "unicode-segmentation", - "vtparse", - "wezterm-bidi", - "wezterm-blob-leases", - "wezterm-color-types", - "wezterm-dynamic", - "wezterm-input-types", - "winapi", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" @@ -4325,9 +3813,7 @@ checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", - "libc", "num-conv", - "num_threads", "powerfmt", "serde_core", "time-core", @@ -4377,9 +3863,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "two-face" -version = "0.4.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e51b6e60e545cfdae5a4639ff423818f52372211a8d9a3e892b4b0761f76b2" +checksum = "3d112cfd41c1387546416bcf49c4ae2a1fcacda0d42c9e97120e9798c90c0923" dependencies = [ "serde", "serde_derive", @@ -4398,12 +3884,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - [[package]] name = "uluru" version = "3.1.0" @@ -4463,12 +3943,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "universal-hash" version = "0.5.1" @@ -4508,19 +3982,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uuid" -version = "1.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" -dependencies = [ - "atomic", - "getrandom 0.4.2", - "js-sys", - "serde_core", - "wasm-bindgen", -] - [[package]] name = "vcpkg" version = "0.2.15" @@ -4534,12 +3995,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "vtparse" -version = "0.6.2" +name = "vte" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" dependencies = [ - "utf8parse", + "memchr", ] [[package]] @@ -4578,34 +4039,23 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", - "rustversion", "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.114" +name = "wasm-bindgen-backend" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", + "log", "proc-macro2", "quote", "syn 2.0.117", @@ -4613,121 +4063,33 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.10.0", - "hashbrown 0.15.2", - "indexmap", - "semver", -] - -[[package]] -name = "wezterm-bidi" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" -dependencies = [ - "log", - "wezterm-dynamic", -] - -[[package]] -name = "wezterm-blob-leases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" -dependencies = [ - "getrandom 0.3.4", - "mac_address", - "serde", - "sha2", - "thiserror 1.0.69", - "uuid", -] - -[[package]] -name = "wezterm-color-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" -dependencies = [ - "csscolorparser", - "deltae", - "lazy_static", - "serde", - "wezterm-dynamic", -] - -[[package]] -name = "wezterm-dynamic" -version = "0.2.1" +name = "wasm-bindgen-macro" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ - "log", - "ordered-float", - "strsim", - "thiserror 1.0.69", - "wezterm-dynamic-derive", + "quote", + "wasm-bindgen-macro-support", ] [[package]] -name = "wezterm-dynamic-derive" -version = "0.1.1" +name = "wasm-bindgen-macro-support" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.117", + "wasm-bindgen-backend", + "wasm-bindgen-shared", ] [[package]] -name = "wezterm-input-types" -version = "0.1.0" +name = "wasm-bindgen-shared" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" -dependencies = [ - "bitflags 1.3.2", - "euclid", - "lazy_static", - "serde", - "wezterm-dynamic", -] +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "which" @@ -5025,94 +4387,6 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.10.0", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "write16" version = "1.0.0" diff --git a/asyncgit/src/graph/walker.rs b/asyncgit/src/graph/walker.rs index 0c0131f66b..d5f7681aad 100644 --- a/asyncgit/src/graph/walker.rs +++ b/asyncgit/src/graph/walker.rs @@ -1,16 +1,141 @@ use super::buffer::Buffer; use super::chunk::{Chunk, Markers}; use super::oids::GraphOids; -use super::{ConnectionType, GraphRow, MAX_LANE_COLORS}; -use crate::sync::{CommitId, CommitInfo}; -use im::Vector; +use super::{ + to_lane_idx, ConnectionType, GraphRow, LaneIdx, MAX_LANE_COLORS, +}; +use crate::sync::CommitId; use std::collections::{HashMap, HashSet}; +/// Get the lanes color index, which cycles through the ste palette. +fn lane_color(lane: usize) -> LaneIdx { + to_lane_idx(lane % MAX_LANE_COLORS) +} + +use bitflags::bitflags; + +bitflags! { + /// The neighboring cells a lane joins to. Overlapping lines + /// are merged through this representation rather than + /// a naive overwrite for readability. + #[derive(Clone, Copy, Default)] + struct Dirs: u8 { + const UP = 0b0001; + const DOWN = 0b0010; + const LEFT = 0b0100; + const RIGHT = 0b1000; + } +} + +impl Dirs { + #[allow(clippy::missing_const_for_fn)] + fn merge(self, other: Self) -> Self { + Self::from_bits_retain(self.bits() | other.bits()) + } + + #[allow(clippy::missing_const_for_fn)] + fn vertical(self) -> bool { + self.intersects(Self::UP | Self::DOWN) + } +} + +/// The sub network of an existing connection glyph +/// `None` represents commit markers, which are never drawn over. +fn conn_dirs(conn: ConnectionType) -> Option { + Some(match conn { + ConnectionType::Vertical | ConnectionType::VerticalDotted => { + Dirs::UP | Dirs::DOWN + } + ConnectionType::MergeBridgeMid => Dirs::LEFT | Dirs::RIGHT, + ConnectionType::MergeBridgeStart => Dirs::DOWN | Dirs::LEFT, + ConnectionType::MergeBridgeEnd => Dirs::DOWN | Dirs::RIGHT, + ConnectionType::BranchUp => Dirs::UP | Dirs::LEFT, + ConnectionType::BranchUpRight => Dirs::UP | Dirs::RIGHT, + ConnectionType::TeeLeft => Dirs::UP | Dirs::DOWN | Dirs::LEFT, + ConnectionType::TeeRight => { + Dirs::UP | Dirs::DOWN | Dirs::RIGHT + } + ConnectionType::TeeUp => Dirs::UP | Dirs::LEFT | Dirs::RIGHT, + ConnectionType::TeeDown => { + Dirs::DOWN | Dirs::LEFT | Dirs::RIGHT + } + ConnectionType::CommitNormal + | ConnectionType::CommitBranch + | ConnectionType::CommitMerge + | ConnectionType::CommitStash + | ConnectionType::CommitUncommitted => return None, + }) +} + +/// Determine the glyph for a cell's connectivity. +/// Vertical lines take precedence in crossed cells. +/// Yet the horizontal bridge continues in +/// the spacer columns either side, so we retain wholeness. +const fn dirs_conn(dirs: Dirs, dotted: bool) -> ConnectionType { + let up = dirs.contains(Dirs::UP); + let down = dirs.contains(Dirs::DOWN); + let left = dirs.contains(Dirs::LEFT); + let right = dirs.contains(Dirs::RIGHT); + match (up, down, left, right) { + (true, true, true, false) => ConnectionType::TeeLeft, + (true, true, false, true) => ConnectionType::TeeRight, + (true, false, true, true) => ConnectionType::TeeUp, + (false, true, true, true) => ConnectionType::TeeDown, + (true, false, true, false) => ConnectionType::BranchUp, + (true, false, false, true) => ConnectionType::BranchUpRight, + (false, true, true, false) => { + ConnectionType::MergeBridgeStart + } + (false, true, false, true) => ConnectionType::MergeBridgeEnd, + (false, false, _, _) => ConnectionType::MergeBridgeMid, + (true, true, _, _) + | (true | false, false | true, false, false) => { + if dotted { + ConnectionType::VerticalDotted + } else { + ConnectionType::Vertical + } + } + } +} + +/// Draw `add` into a cell, merging with whatever is already there. +/// The line with a vertical component is the chosen way with color +/// ensuring lanes stay visually continuous +fn overlay_cell( + cell: &mut Option<(ConnectionType, LaneIdx)>, + add: Dirs, + color: LaneIdx, +) { + if let Some((conn, existing_color)) = cell { + if let Some(existing) = conn_dirs(*conn) { + let is_dotted = + matches!(conn, ConnectionType::VerticalDotted); + + let resolved_color = + if existing.vertical() || !add.vertical() { + *existing_color + } else { + color + }; + + *cell = Some(( + dirs_conn(existing.merge(add), is_dotted), + resolved_color, + )); + } + } else { + *cell = Some((dirs_conn(add, false), color)); + } +} + pub struct GraphWalker { pub buffer: Buffer, pub oids: GraphOids, pub branch_lane_map: HashMap, - pub mergers_map: HashMap, + + /// Maps a merge commit's alias to the alias of its second parent. + pub merge_parents: HashMap, } impl Default for GraphWalker { @@ -25,54 +150,45 @@ impl GraphWalker { buffer: Buffer::new(), oids: GraphOids::new(), branch_lane_map: HashMap::new(), - mergers_map: HashMap::new(), + merge_parents: HashMap::new(), } } - pub fn process(&mut self, commit: &CommitInfo) { - let alias = self.oids.get_or_insert(&commit.id); - let parent_a = commit - .parents - .first() - .map(|p| self.oids.get_or_insert(p)); - let parent_b = - commit.parents.get(1).map(|p| self.oids.get_or_insert(p)); + pub fn process(&mut self, id: CommitId, parents: &[CommitId]) { + let alias = self.oids.get_or_insert(&id); + + let mut mapped_parents = + parents.iter().map(|p| self.oids.get_or_insert(p)); + + // We make the executive and saddening decision to not support octo/mega merges + // TUIs are simply a backwards medium for representing this complexity + let first_parent = mapped_parents.next(); + let second_parent = mapped_parents.next(); let chunk = Chunk { alias: Some(alias), - parent_a, - parent_b, + parent_a: first_parent, + parent_b: second_parent, marker: Markers::Commit, }; - if let Some(b) = parent_b { - self.mergers_map.insert(alias, b); - } + second_parent.map(|b| self.merge_parents.insert(alias, b)); - if parent_a.is_some() && parent_b.is_some() { - let already_tracked = - self.buffer.current.iter().any(|c| { - c.as_ref().is_some_and(|c| { - c.parent_a == parent_b && c.parent_b.is_none() - }) - }); - if !already_tracked { - self.buffer.merger(alias); - } + if first_parent.is_some() + && second_parent.is_some() + && !self.buffer.current.iter().flatten().any(|commit| { + commit.parent_a == second_parent + && commit.parent_b.is_none() + }) { + self.buffer.track_merge_commit(alias); } self.buffer.update(&chunk); } - pub fn snapshot_at( - &self, - global_idx: usize, - ) -> Vector> { - self.buffer - .decompress(global_idx, global_idx) - .into_iter() - .next() - .unwrap_or_default() + /// Number of commits already folded into the graph buffer. + pub fn processed_commits(&self) -> usize { + self.buffer.deltas.len() } pub fn compute_rows( @@ -83,27 +199,34 @@ impl GraphWalker { stashes: &HashSet, head_id: Option<&CommitId>, ) -> Vec { - let end = global_start + commit_range.len().saturating_sub(1); - let snapshots = self.buffer.decompress(global_start, end); + if commit_range.is_empty() { + return Vec::new(); + } + + // decompress one row before the range (when there is one) so + // every row's predecessor state comes from the same replay + let snap_start = global_start.saturating_sub(1); + let end = global_start + commit_range.len() - 1; + let snapshots = self.buffer.decompress(snap_start, end); + let offset = global_start - snap_start; commit_range .iter() .enumerate() .map(|(index, commit_id)| { - let curr = - snapshots.get(index).cloned().unwrap_or_default(); - let prev = if index > 0 { - snapshots.get(index - 1).cloned() - } else if global_start > 0 { - Some(self.snapshot_at(global_start - 1)) - } else { - None - }; + let place = index + offset; + + let current: &[Option] = + snapshots.get(place).map_or(&[], Vec::as_slice); + let previous = place + .checked_sub(1) + .and_then(|i| snapshots.get(i)) + .map(Vec::as_slice); self.render_row( commit_id, - &curr, - prev.as_ref(), + current, + previous, branch_tips, stashes, head_id, @@ -112,17 +235,108 @@ impl GraphWalker { .collect() } + fn draw_merge_bridge( + lanes: &mut [Option<(ConnectionType, LaneIdx)>], + merge_bridge: Option<(usize, usize)>, + commit_lane: usize, + current: &[Option], + previous: Option<&[Option]>, + ) { + let Some((from, to)) = merge_bridge.filter(|(f, t)| f != t) + else { + return; + }; + let target_lane = if from == commit_lane { to } else { from }; + + // only draw the corner continuing upward when the target + // lane already existed on the previous row; a brand-new + // lane starts at this corner + let continues_up = current[target_lane].is_some() + && previous.is_some_and(|prev| { + prev.get(target_lane) == Some(¤t[target_lane]) + }); + + // replace the plain vertical fill_lanes drew for the + // target lane with the precise corner/junction + lanes[target_lane] = None; + let target_dirs = { + let mut d = Dirs::DOWN; + if continues_up { + d |= Dirs::UP; + } + if target_lane > commit_lane { + d |= Dirs::LEFT; + } + if target_lane < commit_lane { + d |= Dirs::RIGHT; + } + d + }; + overlay_cell( + &mut lanes[target_lane], + target_dirs, + lane_color(target_lane), + ); + + Self::draw_bridge_span( + lanes, + from, + to, + lane_color(target_lane), + ); + } + + fn draw_branching_lanes( + lanes: &mut Vec>, + branching_lanes: &[usize], + commit_lane: usize, + ) -> Vec<(LaneIdx, LaneIdx)> { + let mut branches = Vec::new(); + for &branch_lane in branching_lanes { + let from = std::cmp::min(branch_lane, commit_lane); + let to = std::cmp::max(branch_lane, commit_lane); + branches.push((to_lane_idx(from), to_lane_idx(to))); + + if lanes.len() <= to { + lanes.resize(to + 1, None); + } + + Self::draw_bridge_span( + lanes, + from, + to, + lane_color(branch_lane), + ); + let branch_dirs = { + let mut d = Dirs::UP; + if branch_lane == to { + d |= Dirs::LEFT; + } + if branch_lane == from { + d |= Dirs::RIGHT; + } + d + }; + overlay_cell( + &mut lanes[branch_lane], + branch_dirs, + lane_color(branch_lane), + ); + } + branches + } + fn render_row( &self, commit_id: &CommitId, - curr: &Vector>, - prev: Option<&Vector>>, + current: &[Option], + previous: Option<&[Option]>, branch_tips: &HashSet, stashes: &HashSet, head_id: Option<&CommitId>, ) -> GraphRow { let alias = self.oids.get(commit_id); - let commit_lane = curr + let commit_lane = current .iter() .position(|c| { c.as_ref().is_some_and(|chunk| { @@ -132,99 +346,71 @@ impl GraphWalker { .unwrap_or(0); let parent_b_alias = - alias.and_then(|a| self.mergers_map.get(&a).copied()); + alias.and_then(|a| self.merge_parents.get(&a).copied()); let is_merge = parent_b_alias.is_some(); let is_branch_tip = branch_tips.contains(commit_id); let is_stash = stashes.contains(commit_id); - let branching_lanes: Vec = prev + let branching_lanes: Vec = previous .into_iter() .flatten() // Unwrapping the optional, returning empty vec when None .enumerate() .filter(|(i, pc)| { pc.is_some() - && curr.get(*i).is_none_or(Option::is_none) + && current.get(*i).is_none_or(Option::is_none) }) .map(|(i, _)| i) .collect(); - let mut lanes = vec![None; curr.len()]; + let mut lanes = vec![None; current.len()]; - let merge_bridge = is_merge - .then(|| { - let target_lane = curr.iter().position(|c| { + let merge_bridge = if is_merge && parent_b_alias.is_some() { + current + .iter() + .position(|c| { c.as_ref().is_some_and(|chunk| { - parent_b_alias.is_some() - && chunk.parent_a == parent_b_alias + chunk.parent_a == parent_b_alias }) - }); - target_lane.map(|t| { - if t > commit_lane { - (commit_lane, t) - } else { - (t, commit_lane) - } }) - }) - .flatten(); + .map(|t| (commit_lane.min(t), commit_lane.max(t))) + } else { + None + }; self.fill_lanes( &mut lanes, - curr, + current, alias, head_id, is_stash, is_merge, is_branch_tip, - &branching_lanes, ); - if let Some((from, to)) = merge_bridge { - let target_lane = - if from == commit_lane { to } else { from }; - Self::draw_bridge( - &mut lanes, - from, - to, - commit_lane, - target_lane, - ConnectionType::MergeBridgeMid, - ConnectionType::MergeBridgeStart, - ConnectionType::MergeBridgeEnd, - ); - } - - let mut branches = Vec::new(); - for &branch_lane in &branching_lanes { - let from = std::cmp::min(branch_lane, commit_lane); - let to = std::cmp::max(branch_lane, commit_lane); - branches.push((from, to)); - - if lanes.len() <= to { - lanes.resize(to + 1, None); - } + Self::draw_merge_bridge( + &mut lanes, + merge_bridge, + commit_lane, + current, + previous, + ); - Self::draw_bridge( - &mut lanes, - from, - to, - branch_lane, - branch_lane, - ConnectionType::MergeBridgeMid, - ConnectionType::BranchUp, - ConnectionType::BranchUpRight, - ); - } + let branches = Self::draw_branching_lanes( + &mut lanes, + &branching_lanes, + commit_lane, + ); GraphRow { - lane_count: curr.iter().flatten().count(), - commit_lane, + lane_count: to_lane_idx(current.iter().flatten().count()), + commit_lane: to_lane_idx(commit_lane), is_merge, is_branch_tip, is_stash, lanes, - merge_bridge, + merge_bridge: merge_bridge + .map(|(f, t)| (to_lane_idx(f), to_lane_idx(t))), branches, } } @@ -232,21 +418,16 @@ impl GraphWalker { #[allow(clippy::too_many_arguments)] fn fill_lanes( &self, - lanes: &mut [Option<(ConnectionType, usize)>], - curr: &Vector>, + lanes: &mut [Option<(ConnectionType, LaneIdx)>], + curr: &[Option], alias: Option, head_id: Option<&CommitId>, is_stash: bool, is_merge: bool, is_branch_tip: bool, - branching_lanes: &[usize], ) { for (lane_idx, chunk_item) in curr.iter().enumerate() { let Some(chunk) = chunk_item.as_ref() else { - if branching_lanes.contains(&lane_idx) { - lanes[lane_idx] = - Some((ConnectionType::BranchUp, lane_idx % MAX_LANE_COLORS)); - } continue; }; @@ -259,14 +440,16 @@ impl GraphWalker { _ => ConnectionType::CommitNormal, }; - lanes[lane_idx] = Some((conn_type, lane_idx % MAX_LANE_COLORS)); + lanes[lane_idx] = + Some((conn_type, lane_color(lane_idx))); } else { - let is_dotted = head_id - .and_then(|h| self.oids.get(h)) - .is_some_and(|ha| { - chunk.parent_a == Some(ha) - || chunk.parent_b == Some(ha) - }) && lane_idx == 0; + let target_oid = + head_id.and_then(|h| self.oids.get(h)); + + let is_dotted = lane_idx == 0 + && target_oid.is_some() + && (chunk.parent_a == target_oid + || chunk.parent_b == target_oid); let is_orphan = chunk.parent_a.is_none() && chunk.parent_b.is_none(); @@ -281,47 +464,231 @@ impl GraphWalker { ConnectionType::Vertical }; - lanes[lane_idx] = Some((conn, lane_idx % MAX_LANE_COLORS)); + lanes[lane_idx] = Some((conn, lane_color(lane_idx))); } } } - fn draw_bridge( - lanes: &mut [Option<(ConnectionType, usize)>], + /// Lay the horizontal run of a bridge over the lanes strictly + /// between its two ends, merging with whatever each cell already + /// shows. + fn draw_bridge_span( + lanes: &mut [Option<(ConnectionType, LaneIdx)>], from: usize, to: usize, - color_lane: usize, - corner_lane: usize, - mid: ConnectionType, - corner_right: ConnectionType, - corner_left: ConnectionType, + color: LaneIdx, ) { for lane in lanes.iter_mut().take(to).skip(from + 1) { - match lane { - Some(( - ConnectionType::Vertical | ConnectionType::VerticalDotted, - _, - )) => { - *lane = Some((ConnectionType::Cross, color_lane % MAX_LANE_COLORS)); - } - _ => { - *lane = Some((mid, color_lane % MAX_LANE_COLORS)); - } - } + overlay_cell(lane, Dirs::LEFT | Dirs::RIGHT, color); } + } +} - if corner_lane == to { - let new_corner = match (lanes[to], corner_right) { - (Some((ConnectionType::MergeBridgeStart, _)), ConnectionType::BranchUp) => ConnectionType::BranchUpMergeStart, - _ => corner_right, - }; - lanes[to] = Some((new_corner, color_lane % MAX_LANE_COLORS)); - } else if corner_lane == from { - let new_corner = match (lanes[from], corner_left) { - (Some((ConnectionType::MergeBridgeEnd, _)), ConnectionType::BranchUpRight) => ConnectionType::BranchUpRightMergeEnd, - _ => corner_left, - }; - lanes[from] = Some((new_corner, color_lane % MAX_LANE_COLORS)); +#[cfg(test)] +mod tests { + use super::*; + + fn id(n: usize) -> CommitId { + CommitId::from_str_unchecked(&format!("{n:040x}")) + .expect("valid oid") + } + + fn sym(conn: ConnectionType) -> char { + match conn { + ConnectionType::Vertical => '┃', + ConnectionType::VerticalDotted => '╏', + ConnectionType::CommitNormal => 'o', + ConnectionType::CommitBranch => '*', + ConnectionType::CommitMerge => 'M', + ConnectionType::CommitStash => '*', + ConnectionType::CommitUncommitted => '+', + ConnectionType::MergeBridgeStart => '┓', + ConnectionType::MergeBridgeMid => '━', + ConnectionType::MergeBridgeEnd => '┏', + ConnectionType::BranchUp => '┛', + ConnectionType::BranchUpRight => '┗', + ConnectionType::TeeLeft => '┫', + ConnectionType::TeeRight => '┣', + ConnectionType::TeeUp => '┻', + ConnectionType::TeeDown => '┳', + } + } + + /// Render a row the way the UI does: one glyph per lane plus a + /// spacer that carries a bridge's horizontal run. + fn row_to_string(row: &GraphRow) -> String { + let mut out = String::new(); + for (lane_index, conn) in row.lanes.iter().enumerate() { + out.push(conn.map_or(' ', |(c, _)| sym(c))); + + let in_bridge = row + .merge_bridge + .into_iter() + .chain(row.branches.iter().copied()) + .any(|(from, to)| { + lane_index >= usize::from(from) + && lane_index < usize::from(to) + }); + out.push(if in_bridge { '━' } else { ' ' }); + } + out.trim_end().to_string() + } + + /// Walk `history` (newest first, `(commit, parents)`) and render + /// every row. + fn render(history: &[(usize, &[usize])]) -> Vec { + let mut walker = GraphWalker::new(); + let ids: Vec = + history.iter().map(|(c, _)| id(*c)).collect(); + + for (commit, parents) in history { + let parents: Vec = + parents.iter().map(|p| id(*p)).collect(); + walker.process(id(*commit), &parents); + } + + walker + .compute_rows( + &ids, + 0, + &HashSet::new(), + &HashSet::new(), + None, + ) + .iter() + .map(row_to_string) + .collect() + } + + #[test] + fn linear_history() { + let rows = render(&[(1, &[2]), (2, &[3]), (3, &[])]); + assert_eq!(rows, vec!["o", "o", "o"]); + } + + #[test] + fn simple_merge() { + // 1 merges 3 into the line 1 → 2 → 4, 3 → 4 + let rows = + render(&[(1, &[2, 3]), (2, &[4]), (3, &[4]), (4, &[])]); + assert_eq!(rows, vec!["M━┓", "o ┃", "┃ o", "o━┛"]); + } + + #[test] + fn merge_into_tracked_lane_continues_through_corner() { + // 3's merge line joins lane 1 which keeps flowing to 5, + // so the corner must be a junction (┫), not a dead end (┓) + let rows = render(&[ + (1, &[3]), + (2, &[5]), + (3, &[4, 5]), + (4, &[6]), + (5, &[6]), + (6, &[]), + ]); + assert_eq!( + rows, + vec!["o", "┃ o", "M━┫", "o ┃", "┃ o", "o━┛"] + ); + } + + #[test] + fn merge_bridge_crosses_unrelated_lane() { + // 3 (lane 2) merges into 1's line (lane 0) while 2's line + // (lane 1) passes through: the crossed lane keeps its + // vertical instead of being cut by the bridge + let rows = render(&[ + (1, &[4]), + (2, &[5]), + (3, &[6, 4]), + (4, &[7]), + (5, &[7]), + (6, &[7]), + (7, &[]), + ]); + assert_eq!( + rows, + vec![ + "o", + "┃ o", + "┣━┃━M", + "o ┃ ┃", + "┃ o ┃", + "┃ ┃ o", + "o━┻━┛", + ] + ); + } + + #[test] + fn overlapping_branch_bridges_keep_inner_corner() { + // lanes 1 and 2 both close into the commit on lane 0; the + // outer bridge passes through the inner corner (┻) instead + // of erasing it + let rows = + render(&[(1, &[4]), (2, &[4]), (3, &[4]), (4, &[])]); + assert_eq!(rows, vec!["o", "┃ o", "┃ ┃ o", "o━┻━┛"]); + } + + #[test] + fn merge_and_branch_bridges_overlap() { + // commit 3 closes a branch from lane 2 while opening a merge + // to lane 3, crossing lane 1: every line stays continuous + let rows = render(&[ + (1, &[3, 4]), + (2, &[3]), + (3, &[5, 6]), + (4, &[5]), + (5, &[7]), + (6, &[7]), + (7, &[]), + ]); + assert_eq!( + rows, + vec![ + "M━┓", + "┃ ┃ o", + "M━┃━┻━┓", + "┃ o ┃", + "o━┛ ┃", + "┃ o", + "o━━━━━┛", + ] + ); + } + + #[test] + fn crossed_lane_keeps_own_color() { + let rows = &[ + (1usize, &[4usize][..]), + (2, &[5]), + (3, &[6, 4]), + (4, &[7]), + (5, &[7]), + (6, &[7]), + (7, &[]), + ]; + let mut walker = GraphWalker::new(); + let ids: Vec = + rows.iter().map(|(c, _)| id(*c)).collect(); + for (commit, parents) in rows { + let parents: Vec = + parents.iter().map(|p| id(*p)).collect(); + walker.process(id(*commit), &parents); } + let computed = walker.compute_rows( + &ids, + 0, + &HashSet::new(), + &HashSet::new(), + None, + ); + + // row of commit 3: lane 1 is crossed by the merge bridge but + // keeps both its vertical glyph and its own lane color + let crossed = computed[2].lanes[1] + .expect("crossed lane should not be empty"); + assert_eq!(crossed.0, ConnectionType::Vertical); + assert_eq!(crossed.1, lane_color(1)); } } diff --git a/asyncgit/src/revlog.rs b/asyncgit/src/revlog.rs index 607a2babf1..a70c39fd45 100644 --- a/asyncgit/src/revlog.rs +++ b/asyncgit/src/revlog.rs @@ -2,8 +2,8 @@ use crate::{ error::Result, graph::{GraphRow, GraphWalker}, sync::{ - get_commits_info, gix_repo, repo, CommitId, LogWalker, - LogWalkerWithoutFilter, RepoPath, SharedCommitFilterFn, + gix_repo, repo, CommitId, LogWalker, LogWalkerWithoutFilter, + RepoPath, SharedCommitFilterFn, WalkEntry, }, AsyncGitNotification, Error, }; @@ -37,7 +37,7 @@ pub struct AsyncLogResult { /// pub duration: Duration, } -/// +/// Drives the background commit-log walker and exposes graph rows. pub struct AsyncLog { current: Arc>, current_head: Arc>>, @@ -47,6 +47,9 @@ pub struct AsyncLog { filter: Option, partial_extract: AtomicBool, repo: RepoPath, + /// All walk entries collected by the background thread, in walk order. + /// The graph walker reads these lazily, only as far as the viewport requires. + walk_entries: Arc>>, graph_walker: Arc>, } @@ -73,10 +76,18 @@ impl AsyncLog { background: Arc::new(AtomicBool::new(false)), filter, partial_extract: AtomicBool::new(false), + walk_entries: Arc::new(Mutex::new(Vec::new())), graph_walker: Arc::new(Mutex::new(GraphWalker::new())), } } + /// Computes graph rows for `commit_slice` starting at `global_start`. + /// + /// Driven lazily. Processes only as many + /// [`WalkEntry`]s as the viewport requires. + /// + /// Returns `None` when the background walk hasn't reached + /// `global_start + commit_slice.len()` yet. pub fn get_graph_rows( &self, commit_slice: &[CommitId], @@ -85,13 +96,27 @@ impl AsyncLog { stashes: &HashSet, head_id: Option<&CommitId>, ) -> Option> { - let walker_guard = self.graph_walker.lock().ok()?; let needed_end = global_start + commit_slice.len(); - if walker_guard.buffer.deltas.len() < needed_end { - return None; + + let mut walker = self.graph_walker.lock().ok()?; + + { + let entries = self.walk_entries.lock().ok()?; + if entries.len() < needed_end { + return None; + } + + // the walker may already be ahead of the requested range + // (you know, scrolling up), so only feed it entries + // we know it is yet to have seen + let processed = + walker.processed_commits().min(needed_end); + for entry in &entries[processed..needed_end] { + walker.process(entry.id, &entry.parents); + } } - Some(walker_guard.compute_rows( + Some(walker.compute_rows( commit_slice, global_start, branch_tips, @@ -192,7 +217,7 @@ impl AsyncLog { let sender = self.sender.clone(); let arc_pending = Arc::clone(&self.pending); let arc_background = Arc::clone(&self.background); - let arc_graph_walker = Arc::clone(&self.graph_walker); + let arc_walk_entries = Arc::clone(&self.walk_entries); let filter = self.filter.clone(); let repo_path = self.repo.clone(); @@ -209,7 +234,7 @@ impl AsyncLog { &arc_current, &arc_background, &sender, - &arc_graph_walker, + &arc_walk_entries, filter, ) .expect("failed to fetch"); @@ -227,7 +252,7 @@ impl AsyncLog { arc_current: &Arc>, arc_background: &Arc, sender: &Sender, - arc_graph_walker: &Arc>, + arc_walk_entries: &Arc>>, filter: Option, ) -> Result<()> { filter.map_or_else( @@ -237,7 +262,7 @@ impl AsyncLog { arc_current, arc_background, sender, - arc_graph_walker, + arc_walk_entries, ) }, |filter| { @@ -246,107 +271,94 @@ impl AsyncLog { arc_current, arc_background, sender, - arc_graph_walker, filter, ) }, ) } + /// A filtered walk yields a disconnected subset of the history, + /// which the graph cannot represent, so no topology entries are + /// collected here. fn fetch_helper_with_filter( repo_path: &RepoPath, arc_current: &Arc>, arc_background: &Arc, sender: &Sender, - arc_graph_walker: &Arc>, filter: SharedCommitFilterFn, ) -> Result<()> { - let start_time = Instant::now(); - - let mut entries = vec![CommitId::default(); LIMIT_COUNT]; - entries.resize(0, CommitId::default()); - let r = repo(repo_path)?; let mut walker = LogWalker::new(&r, LIMIT_COUNT)?.filter(Some(filter)); - loop { - entries.clear(); - let read = walker.read(&mut entries)?; + Self::walk_loop( + |out| walker.read(out), + arc_current, + arc_background, + sender, + None, + )?; - { - let infos = get_commits_info( - repo_path, - &entries[0..read], - 1000, - )?; - let mut gw = arc_graph_walker.lock()?; - for info in &infos { - gw.process(info); - } - } - - let mut current = arc_current.lock()?; - current.commits.extend(entries.iter()); - current.duration = start_time.elapsed(); + log::trace!("revlog visited: {}", walker.visited()); - if read == 0 { - break; - } - Self::notify(sender); + Ok(()) + } - let sleep_duration = - if arc_background.load(Ordering::Relaxed) { - SLEEP_BACKGROUND - } else { - SLEEP_FOREGROUND - }; + fn fetch_helper_without_filter( + repo_path: &RepoPath, + arc_current: &Arc>, + arc_background: &Arc, + sender: &Sender, + arc_walk_entries: &Arc>>, + ) -> Result<()> { + let mut repo: gix::Repository = gix_repo(repo_path)?; + let mut walker = + LogWalkerWithoutFilter::new(&mut repo, LIMIT_COUNT)?; - thread::sleep(sleep_duration); - } + Self::walk_loop( + |out| walker.read(out), + arc_current, + arc_background, + sender, + Some(arc_walk_entries), + )?; log::trace!("revlog visited: {}", walker.visited()); Ok(()) } - fn fetch_helper_without_filter( - repo_path: &RepoPath, + /// Drives `read` in batches, publishing every batch's commit ids + /// to `arc_current` and (when given) moving the full entries into + /// `walk_entries` for the graph. + fn walk_loop( + mut read: impl FnMut(&mut Vec) -> Result, arc_current: &Arc>, arc_background: &Arc, sender: &Sender, - arc_graph_walker: &Arc>, + walk_entries: Option<&Mutex>>, ) -> Result<()> { let start_time = Instant::now(); - let mut entries = vec![CommitId::default(); LIMIT_COUNT]; - entries.resize(0, CommitId::default()); - - let mut repo: gix::Repository = gix_repo(repo_path)?; - let mut walker = - LogWalkerWithoutFilter::new(&mut repo, LIMIT_COUNT)?; + let mut entries: Vec = + Vec::with_capacity(LIMIT_COUNT); loop { - entries.clear(); - let read = walker.read(&mut entries)?; + let read_count = read(&mut entries)?; { - let infos = get_commits_info( - repo_path, - &entries[0..read], - 1000, - )?; - let mut gw = arc_graph_walker.lock()?; - for info in &infos { - gw.process(info); - } + let mut current = arc_current.lock()?; + current.commits.extend(entries.iter().map(|e| e.id)); + current.duration = start_time.elapsed(); } - let mut current = arc_current.lock()?; - current.commits.extend(entries.iter()); - current.duration = start_time.elapsed(); + if let Some(walk_entries) = walk_entries { + walk_entries.lock()?.append(&mut entries); + } else { + entries.clear(); + } - if read == 0 { + if read_count == 0 { break; } Self::notify(sender); @@ -361,8 +373,6 @@ impl AsyncLog { thread::sleep(sleep_duration); } - log::trace!("revlog visited: {}", walker.visited()); - Ok(()) } @@ -370,6 +380,7 @@ impl AsyncLog { self.current.lock()?.commits.clear(); *self.current_head.lock()? = None; self.partial_extract.store(false, Ordering::Relaxed); + self.walk_entries.lock()?.clear(); *self.graph_walker.lock()? = GraphWalker::new(); Ok(()) } @@ -391,7 +402,6 @@ mod tests { use serial_test::serial; use tempfile::TempDir; - use crate::graph::GraphWalker; use crate::sync::tests::{debug_cmd_print, repo_init}; use crate::sync::RepoPath; use crate::AsyncLog; @@ -419,15 +429,14 @@ mod tests { duration: Duration::default(), })); let arc_background = Arc::new(AtomicBool::new(false)); - let arc_graph_walker = - Arc::new(Mutex::new(GraphWalker::new())); + let arc_walk_entries = Arc::new(Mutex::new(Vec::new())); let result = AsyncLog::fetch_helper_without_filter( &subdir_path, &arc_current, &arc_background, &tx_git, - &arc_graph_walker, + &arc_walk_entries, ); assert_eq!(result.unwrap(), ()); @@ -450,8 +459,7 @@ mod tests { duration: Duration::default(), })); let arc_background = Arc::new(AtomicBool::new(false)); - let arc_graph_walker = - Arc::new(Mutex::new(GraphWalker::new())); + let arc_walk_entries = Arc::new(Mutex::new(Vec::new())); std::env::set_var("GIT_DIR", git_dir); @@ -461,7 +469,7 @@ mod tests { &arc_current, &arc_background, &tx_git, - &arc_graph_walker, + &arc_walk_entries, ); std::env::remove_var("GIT_DIR"); diff --git a/asyncgit/src/sync/logwalker.rs b/asyncgit/src/sync/logwalker.rs index 81a6fd321e..29843271f6 100644 --- a/asyncgit/src/sync/logwalker.rs +++ b/asyncgit/src/sync/logwalker.rs @@ -2,11 +2,25 @@ use super::{CommitId, SharedCommitFilterFn}; use crate::error::Result; use git2::{Commit, Oid, Repository}; use gix::revision::Walk; +use smallvec::SmallVec; use std::{ cmp::Ordering, collections::{BinaryHeap, HashSet}, }; +/// A commit id together with the ids of its TWO parents. +/// +/// The parents come for free during a walk. +/// Collecting here avoid a second, convulted pass. +#[derive(Debug, Clone)] +pub struct WalkEntry { + /// The commit's own unique identifier. + pub id: CommitId, + + /// The commit's parent identifiers. + pub parents: SmallVec<[CommitId; 2]>, +} + struct TimeOrderedCommit<'a>(Commit<'a>); impl Eq for TimeOrderedCommit<'_> {} @@ -70,11 +84,18 @@ impl<'a> LogWalker<'a> { } /// - pub fn read(&mut self, out: &mut Vec) -> Result { + pub fn read( + &mut self, + out: &mut Vec, + ) -> Result { let mut count = 0_usize; while let Some(c) = self.commits.pop() { + let mut parents = SmallVec::new(); for p in c.0.parents() { + if parents.len() < 2 { + parents.push(p.id().into()); + } self.visit(p); } @@ -87,7 +108,7 @@ impl<'a> LogWalker<'a> { }; if commit_should_be_included { - out.push(id); + out.push(WalkEntry { id, parents }); } count += 1; @@ -157,11 +178,22 @@ impl<'a> LogWalkerWithoutFilter<'a> { } /// - pub fn read(&mut self, out: &mut Vec) -> Result { + pub fn read( + &mut self, + out: &mut Vec, + ) -> Result { let mut count = 0_usize; while let Some(Ok(info)) = self.walk.next() { - out.push(info.id.into()); + out.push(WalkEntry { + id: info.id.into(), + parents: info + .parent_ids + .iter() + .take(2) + .map(|id| CommitId::from(*id)) + .collect(), + }); count += 1; @@ -214,7 +246,7 @@ mod tests { walk.read(&mut items).unwrap(); assert_eq!(items.len(), 1); - assert_eq!(items[0], oid2); + assert_eq!(items[0].id, oid2); Ok(()) } @@ -238,11 +270,12 @@ mod tests { let mut walk = LogWalker::new(&repo, 100)?; walk.read(&mut items).unwrap(); - let info = get_commits_info(repo_path, &items, 50).unwrap(); + let ids: Vec = items.iter().map(|e| e.id).collect(); + let info = get_commits_info(repo_path, &ids, 50).unwrap(); dbg!(&info); assert_eq!(items.len(), 2); - assert_eq!(items[0], oid2); + assert_eq!(items[0].id, oid2); let mut items = Vec::new(); walk.read(&mut items).unwrap(); @@ -272,11 +305,12 @@ mod tests { let mut items = Vec::new(); assert!(matches!(walk.read(&mut items), Ok(2))); - let info = get_commits_info(repo_path, &items, 50).unwrap(); + let ids: Vec = items.iter().map(|e| e.id).collect(); + let info = get_commits_info(repo_path, &ids, 50).unwrap(); dbg!(&info); assert_eq!(items.len(), 2); - assert_eq!(items[0], oid2); + assert_eq!(items[0].id, oid2); let mut items = Vec::new(); assert!(matches!(walk.read(&mut items), Ok(0))); @@ -318,7 +352,7 @@ mod tests { walker.read(&mut items).unwrap(); assert_eq!(items.len(), 1); - assert_eq!(items[0], second_commit_id); + assert_eq!(items[0].id, second_commit_id); let mut items = Vec::new(); walker.read(&mut items).unwrap(); @@ -365,7 +399,7 @@ mod tests { walker.read(&mut items).unwrap(); assert_eq!(items.len(), 1); - assert_eq!(items[0], second_commit_id); + assert_eq!(items[0].id, second_commit_id); let log_filter = filter_commit_by_search( LogFilterSearch::new(LogFilterSearchOptions { diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 2cd358d065..2e76a2128e 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -72,7 +72,7 @@ pub use hooks::{ }; pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; -pub use logwalker::{LogWalker, LogWalkerWithoutFilter}; +pub use logwalker::{LogWalker, LogWalkerWithoutFilter, WalkEntry}; pub use merge::{ abort_pending_rebase, abort_pending_state, continue_pending_rebase, merge_branch, merge_commit, merge_msg, @@ -261,13 +261,13 @@ pub mod tests { r: &Repository, max_count: usize, ) -> Vec { - let mut commit_ids = Vec::::new(); + let mut entries = Vec::new(); LogWalker::new(r, max_count) .unwrap() - .read(&mut commit_ids) + .read(&mut entries) .unwrap(); - commit_ids + entries.iter().map(|e| e.id).collect() } /// Same as `repo_init`, but the repo is a bare repo (--bare) From 974c11fdff512b32887822a39c09423c4e55d162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 13 Jun 2026 12:14:30 -0400 Subject: [PATCH 40/60] lane idx and tee connection types cutting down our memory usage from 32+ to EIGHT! also tee connection types --- asyncgit/src/graph/mod.rs | 41 +++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/asyncgit/src/graph/mod.rs b/asyncgit/src/graph/mod.rs index e1c8bee521..d5687ea829 100644 --- a/asyncgit/src/graph/mod.rs +++ b/asyncgit/src/graph/mod.rs @@ -8,34 +8,55 @@ pub use walker::GraphWalker; /// The maximum number of colors to use for graph lanes pub const MAX_LANE_COLORS: usize = 16; -/// The type of connection between nodes in the graph +// Yes, there are repositories where this is exceeded +// Are they very rare? Yes. +// On most terminals can more than 256 lanes even be represneted usefully? Not really. +pub type LaneIdx = u8; + +/// Convert a lane position into the compact [`LaneIdx`] +/// representation. This way we can keep full granularity when computing, +/// but not when storing. +pub(crate) fn to_lane_idx(lane: usize) -> LaneIdx { + LaneIdx::try_from(lane).unwrap_or(LaneIdx::MAX) +} + +/// The type of connection between nodes in the graph. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ConnectionType { Vertical, VerticalDotted, - Cross, CommitNormal, CommitBranch, CommitMerge, CommitStash, CommitUncommitted, + /// a bridge turning down into a lane that starts here MergeBridgeStart, + /// a bridge passing over an empty lane slot MergeBridgeMid, + /// a bridge turning down, lane starting to its right MergeBridgeEnd, - BranchDown, + /// a lane from above turning left into a commit BranchUp, + /// a lane from above turning right into a commit BranchUpRight, - BranchUpMergeStart, - BranchUpRightMergeEnd, + /// a continuing lane absorbing a bridge from its left + TeeLeft, + /// a continuing lane absorbing a bridge from its right + TeeRight, + /// a lane ending from above while a bridge passes through + TeeUp, + /// a lane starting downward while a bridge passes through + TeeDown, } #[derive(Clone, Debug, Default)] pub struct GraphRow { /// Number of active lanes at this commit row - pub lane_count: usize, + pub lane_count: LaneIdx, /// Which lane index this commit sits on - pub commit_lane: usize, + pub commit_lane: LaneIdx, /// Whether this is a merge commit (two parents) pub is_merge: bool, @@ -49,13 +70,13 @@ pub struct GraphRow { /// Connections emitted per lane: /// None = empty space /// Some((ConnectionType, `color_index`)) = draw this connector in this color - pub lanes: Vec>, + pub lanes: Vec>, /// Horizontal merge bridge: if this commit merges rightward, /// (`from_lane`, `to_lane`) — the span to draw ─ ╭ ╮ across - pub merge_bridge: Option<(usize, usize)>, + pub merge_bridge: Option<(LaneIdx, LaneIdx)>, /// Horizontal branch bridges: if this commit spawns branches, /// spans to draw ─ ╭ ╮ across - pub branches: Vec<(usize, usize)>, + pub branches: Vec<(LaneIdx, LaneIdx)>, } From f7ec8b32a0b53c9955cd5ad767cc562fb23333f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 13 Jun 2026 12:14:33 -0400 Subject: [PATCH 41/60] graph symbols and lane count with lane idx --- src/components/utils/graphrow.rs | 6 ++++-- src/components/utils/logitems.rs | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/utils/graphrow.rs b/src/components/utils/graphrow.rs index d626b6640f..e45bae89a7 100644 --- a/src/components/utils/graphrow.rs +++ b/src/components/utils/graphrow.rs @@ -3,7 +3,6 @@ pub const SYM_COMMIT_BRANCH: &str = "*"; pub const SYM_COMMIT_MERGE: &str = "M"; pub const SYM_COMMIT_STASH: &str = "*"; pub const SYM_COMMIT_UNCOMMITTED: &str = "+"; -pub const SYM_CROSS: &str = "╋"; pub const SYM_VERTICAL: &str = "┃"; pub const SYM_VERTICAL_DOTTED: &str = "╏"; pub const SYM_HORIZONTAL: &str = "━"; @@ -11,6 +10,9 @@ pub const SYM_MERGE_BRIDGE_START: &str = "┓"; pub const SYM_MERGE_BRIDGE_MID: &str = "━"; pub const SYM_MERGE_BRIDGE_END: &str = "┏"; pub const SYM_BRANCH_UP: &str = "┛"; -pub const SYM_BRANCH_DOWN: &str = "┓"; pub const SYM_BRANCH_UP_RIGHT: &str = "┗"; +pub const SYM_TEE_LEFT: &str = "┫"; +pub const SYM_TEE_RIGHT: &str = "┣"; +pub const SYM_TEE_UP: &str = "┻"; +pub const SYM_TEE_DOWN: &str = "┳"; pub const SYM_SPACE: &str = " "; diff --git a/src/components/utils/logitems.rs b/src/components/utils/logitems.rs index 1ff46079a6..1025667d5c 100644 --- a/src/components/utils/logitems.rs +++ b/src/components/utils/logitems.rs @@ -83,7 +83,7 @@ impl LogEntry { } } -/// +/// A batch of parsed log entries with an index offset. #[derive(Default)] pub struct ItemBatch { index_offset: Option, @@ -154,9 +154,9 @@ impl ItemBatch { /// pub fn set_graph_rows(&mut self, rows: Vec) { - let mut max = 0; + let mut max: usize = 0; for (entry, row) in self.items.iter_mut().zip(rows) { - max = max.max(row.lane_count); + max = max.max(row.lane_count.into()); entry.graph = Some(row); } self.max_lane = max; From 33d9a185700f5bcd1390ee7132065e617dc9a6df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 13 Jun 2026 12:14:36 -0400 Subject: [PATCH 42/60] commitlist graph rendering refactor big focuses on readability and reduced repition --- src/components/commitlist.rs | 356 +++++++++++++++++------------------ 1 file changed, 177 insertions(+), 179 deletions(-) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 47c32675b1..5d84400c7f 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -1,10 +1,10 @@ use super::utils::graphrow::{ - SYM_BRANCH_DOWN, SYM_BRANCH_UP, SYM_BRANCH_UP_RIGHT, SYM_COMMIT, + SYM_BRANCH_UP, SYM_BRANCH_UP_RIGHT, SYM_COMMIT, SYM_COMMIT_BRANCH, SYM_COMMIT_MERGE, SYM_COMMIT_STASH, - SYM_COMMIT_UNCOMMITTED, SYM_CROSS, SYM_HORIZONTAL, - SYM_MERGE_BRIDGE_END, SYM_MERGE_BRIDGE_MID, - SYM_MERGE_BRIDGE_START, SYM_SPACE, SYM_VERTICAL, - SYM_VERTICAL_DOTTED, + SYM_COMMIT_UNCOMMITTED, SYM_HORIZONTAL, SYM_MERGE_BRIDGE_END, + SYM_MERGE_BRIDGE_MID, SYM_MERGE_BRIDGE_START, SYM_SPACE, + SYM_TEE_DOWN, SYM_TEE_LEFT, SYM_TEE_RIGHT, SYM_TEE_UP, + SYM_VERTICAL, SYM_VERTICAL_DOTTED, }; use super::utils::logitems::{ItemBatch, LogEntry}; use crate::{ @@ -40,6 +40,7 @@ use ratatui::{ Frame, }; use std::borrow::Cow; +use std::collections::HashSet; use std::{ cell::Cell, cmp, collections::BTreeMap, rc::Rc, time::Instant, }; @@ -56,7 +57,7 @@ const GRAPH_COLORS: &[Color] = &[ Color::Red, ]; -/// +/// Renders the commit log with a side-bar graph. pub struct CommitList { repo: RepoPathRef, title: Box, @@ -108,6 +109,16 @@ impl CommitList { } } + /// Whether the commit graph column is currently visible. + pub const fn is_graph_visible(&self) -> bool { + self.show_graph + } + + /// Whether the loaded window already has up-to-date graph rows. + pub const fn is_graph_ready(&self) -> bool { + self.items.graph_ready + } + /// pub fn get_loaded_slice(&self) -> (Vec, usize) { let offset = self.items.index_offset(); @@ -238,6 +249,9 @@ impl CommitList { .or_default() .push(local_branch); } + + // branch tips and head are baked into graph rows + self.items.graph_ready = false; } /// @@ -253,6 +267,9 @@ impl CommitList { .or_default() .push(remote_branch); } + + // branch tips are baked into graph rows + self.items.graph_ready = false; } /// @@ -490,7 +507,6 @@ impl CommitList { } fn build_graph_spans<'a>( - &self, row: &'a GraphRow, graph_col_width: usize, empty_lanes: &std::collections::HashSet, @@ -503,73 +519,47 @@ impl CommitList { } let (sym, graph_color) = match conn { None => (SYM_SPACE, Color::Reset), - Some((ConnectionType::Vertical, color_idx)) => ( - SYM_VERTICAL, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::VerticalDotted, color_idx)) => ( - SYM_VERTICAL_DOTTED, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::Cross, color_idx)) => ( - SYM_CROSS, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::BranchUpMergeStart, color_idx)) => ( - SYM_BRANCH_UP, // Reusing '┛' - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some(( - ConnectionType::BranchUpRightMergeEnd, - color_idx, - )) => ( - SYM_BRANCH_UP_RIGHT, // Reusing '┗' - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::CommitNormal, color_idx)) => ( - SYM_COMMIT, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::CommitBranch, color_idx)) => ( - SYM_COMMIT_BRANCH, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::CommitMerge, color_idx)) => ( - SYM_COMMIT_MERGE, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::CommitStash, color_idx)) => ( - SYM_COMMIT_STASH, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::CommitUncommitted, color_idx)) => ( - SYM_COMMIT_UNCOMMITTED, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::MergeBridgeStart, color_idx)) => ( - SYM_MERGE_BRIDGE_START, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::MergeBridgeMid, color_idx)) => ( - SYM_MERGE_BRIDGE_MID, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::MergeBridgeEnd, color_idx)) => ( - SYM_MERGE_BRIDGE_END, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::BranchDown, color_idx)) => ( - SYM_BRANCH_DOWN, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::BranchUp, color_idx)) => ( - SYM_BRANCH_UP, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), - Some((ConnectionType::BranchUpRight, color_idx)) => ( - SYM_BRANCH_UP_RIGHT, - GRAPH_COLORS[color_idx % GRAPH_COLORS.len()], - ), + Some((conn_type, color_idx)) => { + let color = GRAPH_COLORS[usize::from(*color_idx) + % GRAPH_COLORS.len()]; + let sym = match conn_type { + ConnectionType::Vertical => SYM_VERTICAL, + ConnectionType::VerticalDotted => { + SYM_VERTICAL_DOTTED + } + ConnectionType::TeeLeft => SYM_TEE_LEFT, + ConnectionType::TeeRight => SYM_TEE_RIGHT, + ConnectionType::TeeUp => SYM_TEE_UP, + ConnectionType::TeeDown => SYM_TEE_DOWN, + ConnectionType::BranchUpRight => { + SYM_BRANCH_UP_RIGHT + } + ConnectionType::CommitNormal => SYM_COMMIT, + ConnectionType::CommitBranch => { + SYM_COMMIT_BRANCH + } + ConnectionType::CommitMerge => { + SYM_COMMIT_MERGE + } + ConnectionType::CommitStash => { + SYM_COMMIT_STASH + } + ConnectionType::CommitUncommitted => { + SYM_COMMIT_UNCOMMITTED + } + ConnectionType::MergeBridgeStart => { + SYM_MERGE_BRIDGE_START + } + ConnectionType::MergeBridgeMid => { + SYM_MERGE_BRIDGE_MID + } + ConnectionType::MergeBridgeEnd => { + SYM_MERGE_BRIDGE_END + } + ConnectionType::BranchUp => SYM_BRANCH_UP, + }; + (sym, color) + } }; spans.push(Span::styled( sym, @@ -580,24 +570,26 @@ impl CommitList { let mut is_bridge_lane = false; let mut spacer_color = graph_color; - if let Some((from, to)) = row.merge_bridge { - if lane_index >= from && lane_index < to { - is_bridge_lane = true; - spacer_color = GRAPH_COLORS - [row.commit_lane % GRAPH_COLORS.len()]; - } - } + let commit_lane = usize::from(row.commit_lane); + + let edges = row + .merge_bridge + .into_iter() + .chain(row.branches.iter().copied()); + + for (st, fin) in edges { + let (start, finish) = + (usize::from(st), usize::from(fin)); - for &(from, to) in &row.branches { - if lane_index >= from && lane_index < to { + if (start..finish).contains(&lane_index) { is_bridge_lane = true; - let branch_lane = if row.commit_lane == from { - to + let target_lane = if commit_lane == start { + finish } else { - from + start }; spacer_color = GRAPH_COLORS - [branch_lane % GRAPH_COLORS.len()]; + [target_lane % GRAPH_COLORS.len()]; } } @@ -690,7 +682,7 @@ impl CommitList { empty_lanes: &std::collections::HashSet, ) { if let Some(ref row) = e.graph { - txt.extend(self.build_graph_spans( + txt.extend(Self::build_graph_spans( row, self.graph_col_width.get(), empty_lanes, @@ -787,111 +779,114 @@ impl CommitList { fn get_text(&self, height: usize, width: usize) -> Vec> { let selection = self.relative_selection(); - - let mut txt: Vec = Vec::with_capacity(height); - - let mut empty_lanes = std::collections::HashSet::new(); + let now = Local::now(); + let any_marked = !self.marked.is_empty(); if self.show_graph { - let mut max_lane_in_view = 0; - for e in self + let view = self .items .iter() .skip(self.scroll_top.get()) - .take(height) - { - if let Some(row) = &e.graph { - max_lane_in_view = - max_lane_in_view.max(row.lanes.len()); - } - } - - // Assume all lanes up to max_lane_in_view are empty - empty_lanes.extend(0..max_lane_in_view); - - // Remove lanes that have content - for e in self - .items - .iter() - .skip(self.scroll_top.get()) - .take(height) - { - if let Some(row) = &e.graph { - for (lane_idx, conn) in - row.lanes.iter().enumerate() - { - if !matches!( - conn, - None | Some(( - ConnectionType::MergeBridgeMid, - _ - )) - ) { - empty_lanes.remove(&lane_idx); - } - } - } - } + .take(height); + + let max_lane = view + .clone() + .filter_map(|e| e.graph.as_ref()) + .map(|row| row.lanes.len()) + .max() + .unwrap_or(0); + + let empty_lanes: std::collections::HashSet = (0..max_lane) + .filter(|&index| { + view.clone().filter_map(|entry| entry.graph.as_ref()).all(|row| { + row.lanes.get(index).is_none_or(|conn| { + matches!(conn, None | Some((ConnectionType::MergeBridgeMid, _))) + }) + }) + }) + .collect(); + + self.graph_col_width.set( + ((max_lane.saturating_sub(empty_lanes.len())) * 2) + .max(2), + ); - let width = ((max_lane_in_view - .saturating_sub(empty_lanes.len())) - * 2) - .max(2); - self.graph_col_width.set(width); + self.collect_entries( + height, + width, + selection, + now, + any_marked, + &empty_lanes, + ) } else { self.graph_col_width.set(0); + self.collect_entries( + height, + width, + selection, + now, + any_marked, + &HashSet::new(), + ) } + } - let any_marked = !self.marked.is_empty(); - let now = Local::now(); - - for (idx, e) in self - .items + fn collect_entries( + &self, + height: usize, + width: usize, + selection: usize, + now: DateTime, + any_marked: bool, + empty_lanes: &HashSet, + ) -> Vec> { + self.items .iter() .skip(self.scroll_top.get()) .take(height) .enumerate() - { - let tags = - self.tags.as_ref().and_then(|t| t.get(&e.id)).map( - |tags| { + .map(|(index, entry)| { + let tags = self + .tags + .as_ref() + .and_then(|t| t.get(&entry.id)) + .map(|tags| { tags.iter() - .map(|t| format!("<{}>", t.name)) + .map(|tag| format!("<{}>", tag.name)) .join(" ") - }, - ); - - let local_branches = - self.local_branches.get(&e.id).map(|local_branch| { - local_branch - .iter() - .map(|local_branch| { - format!("{{{0}}}", local_branch.name) - }) - .join(" ") - }); - - let marked = if any_marked { - self.is_marked(&e.id) - } else { - None - }; - - txt.push(self.get_entry_to_add( - e, - idx + self.scroll_top.get() == selection, - tags, - local_branches, - self.remote_branches_string(e), - &self.theme, - width.into(), - now, - marked, - &empty_lanes, - )); - } - - txt + }); + + let local_branches = self + .local_branches + .get(&entry.id) + .map(|local_branch| { + local_branch + .iter() + .map(|branch| { + format!("{{{}}}", branch.name) + }) + .join(" ") + }); + + let marked = any_marked + .then(|| self.is_marked(&entry.id)) + .flatten(); + + self.get_entry_to_add( + entry, + index + self.scroll_top.get() == selection, + tags, + local_branches, + self.remote_branches_string(entry), + &self.theme, + width, + now, + marked, + empty_lanes, + ) + }) + .collect() } fn remote_branches_string(&self, e: &LogEntry) -> Option { @@ -1358,7 +1353,6 @@ mod tests { #[test] fn test_build_graph_spans() { - let cl = CommitList::default(); let row = GraphRow { lane_count: 1, commit_lane: 0, @@ -1369,7 +1363,11 @@ mod tests { merge_bridge: None, branches: vec![], }; - let spans = cl.build_graph_spans(&row, 1); + let spans = CommitList::build_graph_spans( + &row, + 1, + &std::collections::HashSet::new(), + ); assert_eq!(spans.len(), 2); assert_eq!(spans[0].content, Cow::from(SYM_COMMIT)); From 00f45043036892e624d6187379ac0fa9afe72a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 13 Jun 2026 12:14:39 -0400 Subject: [PATCH 43/60] revlog tab graph method extraction Extract update_graph_rows and gate behind is_graph_visible/ready. --- src/tabs/revlog.rs | 76 ++++++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 23687c5493..ef2d6dc648 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -60,7 +60,7 @@ enum LogSearch { Results(LogSearchResult), } -/// +/// Top-level revlog tab that ties together the commit list, details, and search. pub struct Revlog { repo: RepoPathRef, commit_details: CommitDetailsComponent, @@ -135,35 +135,10 @@ impl Revlog { self.list .refresh_extend_data(self.git_log.extract_items()?); - let (slice, global_start) = self.list.get_loaded_slice(); - if !slice.is_empty() { - let mut branch_tips = HashSet::new(); - let mut head_id = None; - - for (id, branches) in self.list.local_branches() { - branch_tips.insert(*id); - if head_id.is_none() - && branches.iter().any(|b| { - b.local_details() - .is_some_and(|d| d.is_head) - }) { - head_id = Some(*id); - } - } - branch_tips - .extend(self.list.remote_branches().keys()); - - let stashes = HashSet::new(); - - if let Some(rows) = self.git_log.get_graph_rows( - &slice, - global_start, - &branch_tips, - &stashes, - head_id.as_ref(), - ) { - self.list.set_graph_rows(rows); - } + if self.list.is_graph_visible() + && !self.list.is_graph_ready() + { + self.update_graph_rows(); } self.git_tags.request(Duration::from_secs(3), false)?; @@ -182,6 +157,47 @@ impl Revlog { Ok(()) } + /// Computes graph rows for the current commit window. Rows + /// don't depend on the selection + /// so its is skipped while + /// [`CommitList::is_graph_ready`] holds the bag. + fn update_graph_rows(&mut self) { + let (slice, global_start) = self.list.get_loaded_slice(); + if slice.is_empty() { + return; + } + + let head_id = self.list.local_branches().iter().find_map( + |(id, branches)| { + let has_head = branches.iter().any(|branch| { + branch.local_details().is_some_and(|b| b.is_head) + }); + has_head.then_some(*id) + }, + ); + + let branch_tips: HashSet<_> = self + .list + .local_branches() + .keys() + .chain(self.list.remote_branches().keys()) + .copied() + .collect(); + + // TODO: include stashes (heh) + let stashes = HashSet::new(); + + if let Some(rows) = self.git_log.get_graph_rows( + &slice, + global_start, + &branch_tips, + &stashes, + head_id.as_ref(), + ) { + self.list.set_graph_rows(rows); + } + } + /// pub fn update_git( &mut self, From db1924cdee9d17dadfe1f46d891b5bbefc66af94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 13 Jun 2026 12:14:44 -0400 Subject: [PATCH 44/60] snapshot test infrastructure sets up insta for reliable graph snapshots --- Cargo.lock | 860 +++++++++++++++++- src/main.rs | 2 +- ...gitui__gitui__tests__log_graph_hidden.snap | 17 + ...gitui__gitui__tests__log_graph_linear.snap | 17 + ...__gitui__tests__log_graph_with_branch.snap | 19 + 5 files changed, 868 insertions(+), 47 deletions(-) create mode 100644 src/snapshots/gitui__gitui__tests__log_graph_hidden.snap create mode 100644 src/snapshots/gitui__gitui__tests__log_graph_linear.snap create mode 100644 src/snapshots/gitui__gitui__tests__log_graph_with_branch.snap diff --git a/Cargo.lock b/Cargo.lock index 12b2f61b0c..2eae56e50e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,6 +131,15 @@ version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d26c0798709bf56d9ed84af3" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arc-swap" version = "1.8.0" @@ -150,7 +159,7 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" name = "asyncgit" version = "0.28.1" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "crossbeam-channel", "dirs", "easy-cast", @@ -176,6 +185,15 @@ dependencies = [ "url", ] +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -235,6 +253,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec 0.6.3", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -244,6 +271,12 @@ dependencies = [ "bit-vec 0.8.0", ] +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bit-vec" version = "0.8.0" @@ -258,9 +291,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] @@ -330,6 +363,18 @@ dependencies = [ "unicode-width 0.1.14", ] +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "byteorder" version = "1.5.0" @@ -486,6 +531,15 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -550,7 +604,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "crossterm_winapi", "mio", "parking_lot", @@ -561,6 +615,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.13.0", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix 1.1.3", + "serde", + "signal-hook", + "signal-hook-mio", + "winapi", +] + [[package]] name = "crossterm_winapi" version = "0.9.1" @@ -592,6 +665,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "csscolorparser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +dependencies = [ + "lab", + "phf", +] + [[package]] name = "ctr" version = "0.9.2" @@ -676,6 +759,12 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "deltae" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" + [[package]] name = "der" version = "0.7.9" @@ -695,6 +784,28 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + [[package]] name = "diff" version = "0.1.13" @@ -886,6 +997,25 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set 0.5.3", + "regex", +] + [[package]] name = "fancy-regex" version = "0.16.2" @@ -897,6 +1027,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + [[package]] name = "faster-hex" version = "0.10.0" @@ -929,6 +1065,17 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + [[package]] name = "filetime" version = "0.2.25" @@ -949,6 +1096,18 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "finl_unicode" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.1.1" @@ -1084,6 +1243,7 @@ dependencies = [ "libc", "r-efi 6.0.0", "wasip2", + "wasip3", ] [[package]] @@ -1138,7 +1298,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddddbf932745a6be37109b6112d3ee09696106f848449069d3a57bba937ab82e" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "libc", "libgit2-sys", "log", @@ -1179,7 +1339,7 @@ dependencies = [ "asyncgit", "backtrace", "base64", - "bitflags 2.10.0", + "bitflags 2.13.0", "bugreport", "bwrap", "bytesize", @@ -1204,6 +1364,7 @@ dependencies = [ "parking_lot_core", "pretty_assertions", "ratatui", + "ratatui-textarea", "rayon-core", "ron", "scopeguard", @@ -1373,7 +1534,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "563361198101cedc975fe5760c91ac2e4126eec22216e81b659b45289feaf1ea" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "bstr", "gix-path", "libc", @@ -1522,7 +1683,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b03e6cd88cc0dc1eafa1fddac0fb719e4e74b6ea58dd016e71125fde4a326bee" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "bstr", "gix-features", "gix-path", @@ -1570,7 +1731,7 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e31c6b3664efe5916c539c50e610f9958f2993faf8e29fa5a40fb80b6ac8486a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "bstr", "filetime", "fnv", @@ -1706,7 +1867,7 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3df6fd8e514d8b99ec5042ee17909a17750ccf54d0b8b30c850954209c800322" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "bstr", "gix-attributes", "gix-config-value", @@ -1788,7 +1949,7 @@ version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c719cf7d669439e1fca735bd1c4de54d43c5d30e8883fd6063c4924b213d70c9" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "bstr", "gix-commitgraph", "gix-date", @@ -1822,7 +1983,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "beeb3bc63696cf7acb5747a361693ebdbcaf25b5d27d2308f38e9782983e7bce" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "gix-path", "libc", "windows-sys 0.61.2", @@ -1919,7 +2080,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37f8b53b4c56b01c43a4491c4edfe2ce66c654eb86232205172ceb1650d21c55" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "gix-commitgraph", "gix-date", "gix-hash", @@ -2026,6 +2187,17 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + [[package]] name = "heapless" version = "0.8.0" @@ -2042,6 +2214,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hmac" version = "0.12.1" @@ -2192,6 +2370,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -2236,6 +2420,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -2250,7 +2436,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "inotify-sys", "libc", ] @@ -2386,6 +2572,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kasuari" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481b4381c813cebeca86bd55c781d21f902f34cf927ec08d6df3dfebcfd2002" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.18", +] + [[package]] name = "kqueue" version = "1.1.1" @@ -2415,6 +2611,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "lab" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" + [[package]] name = "lazy_static" version = "1.5.0" @@ -2424,6 +2626,12 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.180" @@ -2455,7 +2663,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "libc", "redox_syscall", ] @@ -2472,6 +2680,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "line-clipping" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" +dependencies = [ + "bitflags 2.13.0", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -2490,6 +2707,12 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -2550,6 +2773,27 @@ dependencies = [ "libc", ] +[[package]] +name = "memmem" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.7" @@ -2571,13 +2815,36 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "notify" version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "fsevent-sys", "inotify", "kqueue", @@ -2716,7 +2983,7 @@ version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "libc", "once_cell", "onig_sys", @@ -2819,6 +3086,30 @@ dependencies = [ "sha2", ] +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "libm", + "palette_derive", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -2867,54 +3158,131 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "phf" -version = "0.11.3" +name = "pest" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ - "phf_shared", + "memchr", + "ucd-trie", ] [[package]] -name = "phf_shared" -version = "0.11.3" +name = "pest_derive" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" dependencies = [ - "siphasher", + "pest", + "pest_generator", ] [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "pest_generator" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pest_meta" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] [[package]] -name = "pkcs1" -version = "0.7.5" +name = "phf" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "der", - "pkcs8", - "spki", + "phf_macros", + "phf_shared", ] [[package]] -name = "pkcs8" -version = "0.10.2" +name = "phf_codegen" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "der", - "spki", + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", ] [[package]] @@ -2999,6 +3367,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -3050,6 +3428,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.5" @@ -3110,6 +3494,74 @@ dependencies = [ "palette", "serde", "strum", + "thiserror 2.0.18", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + +[[package]] +name = "ratatui-crossterm" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2867bedcbd6a690ca4f8672a687b730ec07660c79844517b084311b529980c" +dependencies = [ + "cfg-if", + "crossterm 0.28.1", + "crossterm 0.29.0", + "instability", + "ratatui-core", +] + +[[package]] +name = "ratatui-termion" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c16cc35a9d9114e0b2bb4b22018b96ae7f5fe60e2595dc73e622b4e78624835" +dependencies = [ + "instability", + "ratatui-core", + "termion", +] + +[[package]] +name = "ratatui-termwiz" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "386b8ff8f74ed749509391c56d549761a2fcdb408e1f42e467286bcb7dac8967" +dependencies = [ + "ratatui-core", + "termwiz", +] + +[[package]] +name = "ratatui-textarea" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de236b7cc74b3f7dea227b3fbad97bf459cddf552b6503d888fb9a106eda59ab" +dependencies = [ + "ratatui-core", + "ratatui-crossterm", + "ratatui-widgets", + "unicode-width 0.2.0", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef4f17dd7ac3abf5adc2b920a03c61eee4bfe6a88fa5191936895525371d79c" +dependencies = [ + "bitflags 2.13.0", + "hashbrown 0.17.1", + "indoc", + "instability", + "itertools", + "line-clipping", + "ratatui-core", + "serde", + "strum", + "time", "unicode-segmentation", "unicode-width 0.2.0", ] @@ -3140,7 +3592,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", ] [[package]] @@ -3199,7 +3651,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "once_cell", "serde", "serde_derive", @@ -3249,7 +3701,7 @@ version = "0.38.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -3262,7 +3714,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.11.0", @@ -3764,6 +4216,90 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "terminfo" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" +dependencies = [ + "fnv", + "nom", + "phf", + "phf_codegen", +] + +[[package]] +name = "termion" +version = "4.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44138a9ae08f0f502f24104d82517ef4da7330c35acd638f1f29d3cd5475ecb" +dependencies = [ + "libc", + "numtoa", + "serde", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "termwiz" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" +dependencies = [ + "anyhow", + "base64", + "bitflags 2.13.0", + "fancy-regex 0.11.0", + "filedescriptor", + "finl_unicode", + "fixedbitset", + "hex", + "lazy_static", + "libc", + "log", + "memmem", + "nix", + "num-derive", + "num-traits", + "ordered-float", + "pest", + "pest_derive", + "phf", + "serde", + "sha2", + "signal-hook", + "siphasher", + "terminfo", + "termios", + "thiserror 1.0.69", + "ucd-trie", + "unicode-segmentation", + "vtparse", + "wezterm-bidi", + "wezterm-blob-leases", + "wezterm-color-types", + "wezterm-dynamic", + "wezterm-input-types", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" @@ -3813,7 +4349,9 @@ checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde_core", "time-core", @@ -3884,6 +4422,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "uluru" version = "3.1.0" @@ -3943,6 +4487,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -3982,6 +4532,19 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" +dependencies = [ + "atomic", + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -4003,6 +4566,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "vtparse" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" +dependencies = [ + "utf8parse", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -4091,6 +4663,114 @@ version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.13.0", + "hashbrown 0.15.2", + "indexmap", + "semver", +] + +[[package]] +name = "wezterm-bidi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" +dependencies = [ + "log", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-blob-leases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" +dependencies = [ + "getrandom 0.3.4", + "mac_address", + "serde", + "sha2", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "wezterm-color-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" +dependencies = [ + "csscolorparser", + "deltae", + "lazy_static", + "serde", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-dynamic" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" +dependencies = [ + "log", + "ordered-float", + "strsim", + "thiserror 1.0.69", + "wezterm-dynamic-derive", +] + +[[package]] +name = "wezterm-dynamic-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "wezterm-input-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" +dependencies = [ + "bitflags 1.3.2", + "euclid", + "lazy_static", + "serde", + "wezterm-dynamic", +] + [[package]] name = "which" version = "8.0.0" @@ -4387,6 +5067,94 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.13.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/src/main.rs b/src/main.rs index fd662950a2..cb5fc69be6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -114,7 +114,7 @@ type Terminal = ratatui::Terminal>; static TICK_INTERVAL: Duration = Duration::from_secs(5); static SPINNER_INTERVAL: Duration = Duration::from_millis(80); -/// +/// Events sent to the event-loop to wake up the UI. #[derive(Clone)] pub enum QueueEvent { Tick, diff --git a/src/snapshots/gitui__gitui__tests__log_graph_hidden.snap b/src/snapshots/gitui__gitui__tests__log_graph_hidden.snap new file mode 100644 index 0000000000..bab83a0898 --- /dev/null +++ b/src/snapshots/gitui__gitui__tests__log_graph_hidden.snap @@ -0,0 +1,17 @@ +--- +source: src/gitui.rs +assertion_line: 359 +expression: terminal.backend() +--- +" Status [1] | Log [2] | Files [3] | Stashing [4] | Stashes [5][TEMP_FILE] " +" ──────────────────────────────────────────────────────────────────────────────────────── " +"┌Commit 3/3──────────────────────────────────────────────────────────────────────────────┐" +"│[COMMIT] <1m ago name commit B █" +"│[COMMIT] <1m ago name commit A ║" +"│[COMMIT] <1m ago name initial ║" +"│ ║" +"│ ║" +"│ ║" +"│ ║" +"└────────────────────────────────────────────────────────────────────────────────────────┘" +"Scroll [↑↓] Mark [˽] Toggle Graph [g] Details [⏎] Branches [b] Compare [⇧C] more [.]" diff --git a/src/snapshots/gitui__gitui__tests__log_graph_linear.snap b/src/snapshots/gitui__gitui__tests__log_graph_linear.snap new file mode 100644 index 0000000000..a03daefa14 --- /dev/null +++ b/src/snapshots/gitui__gitui__tests__log_graph_linear.snap @@ -0,0 +1,17 @@ +--- +source: src/gitui.rs +assertion_line: 341 +expression: terminal.backend() +--- +" Status [1] | Log [2] | Files [3] | Stashing [4] | Stashes [5][TEMP_FILE] " +" ──────────────────────────────────────────────────────────────────────────────────────── " +"┌Commit 3/3──────────────────────────────────────────────────────────────────────────────┐" +"│o [COMMIT] <1m ago name commit B █" +"│o [COMMIT] <1m ago name commit A ║" +"│o [COMMIT] <1m ago name initial ║" +"│ ║" +"│ ║" +"│ ║" +"│ ║" +"└────────────────────────────────────────────────────────────────────────────────────────┘" +"Scroll [↑↓] Mark [˽] Toggle Graph [g] Details [⏎] Branches [b] Compare [⇧C] more [.]" diff --git a/src/snapshots/gitui__gitui__tests__log_graph_with_branch.snap b/src/snapshots/gitui__gitui__tests__log_graph_with_branch.snap new file mode 100644 index 0000000000..b4033ba5ba --- /dev/null +++ b/src/snapshots/gitui__gitui__tests__log_graph_with_branch.snap @@ -0,0 +1,19 @@ +--- +source: src/gitui.rs +assertion_line: 375 +expression: terminal.backend() +--- +" Status [1] | Log [2] | Files [3] | Stashing [4] | Stashes [5][TEMP_FILE] " +" ──────────────────────────────────────────────────────────────────────────────────────── " +"┌Commit 4/4──────────────────────────────────────────────────────────────────────────────┐" +"│M━┓ [COMMIT] <1m ago name merge C into main █" +"│o ┃ [COMMIT] <1m ago name commit A ║" +"│┃ o [COMMIT] <1m ago name commit C ║" +"│o━┛ [COMMIT] <1m ago name initial ║" +"│ ║" +"│ ║" +"│ ║" +"│ ║" +"│ ║" +"└────────────────────────────────────────────────────────────────────────────────────────┘" +"Scroll [↑↓] Mark [˽] Toggle Graph [g] Details [⏎] Branches [b] Compare [⇧C] more [.]" From 515724ccff882f36a6949e92d46209181d22aafb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 13 Jun 2026 12:20:32 -0400 Subject: [PATCH 45/60] trailing comma and minor cleanup --- filetreelist/src/lib.rs | 1 - src/app.rs | 6 +++--- src/components/status_tree.rs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/filetreelist/src/lib.rs b/filetreelist/src/lib.rs index 3e80065aba..74ccf6aed4 100644 --- a/filetreelist/src/lib.rs +++ b/filetreelist/src/lib.rs @@ -1,4 +1,3 @@ -// #![forbid(missing_docs)] #![forbid(unsafe_code)] #![deny( mismatched_lifetime_syntaxes, diff --git a/src/app.rs b/src/app.rs index ddb157af3f..435b5eedde 100644 --- a/src/app.rs +++ b/src/app.rs @@ -138,9 +138,9 @@ impl Environment { use crossbeam_channel::unbounded; Self { queue: Queue::new(), - theme: Default::default(), - key_config: Default::default(), - repo: RefCell::new(RepoPath::Path(Default::default())), + theme: Rc::default(), + key_config: Rc::default(), + repo: RefCell::new(RepoPath::Path(PathBuf::default())), options: Rc::new(RefCell::new(Options::test_env())), sender_git: unbounded().0, sender_app: unbounded().0, diff --git a/src/components/status_tree.rs b/src/components/status_tree.rs index ac4fc9f6a8..48935ca777 100644 --- a/src/components/status_tree.rs +++ b/src/components/status_tree.rs @@ -22,7 +22,7 @@ use std::{borrow::Cow, cell::Cell, path::Path}; //TODO: use new `filetreelist` crate -/// +/// Renders the working-tree status as a tree. #[allow(clippy::struct_excessive_bools)] pub struct StatusTreeComponent { title: String, From cec6f89428577fe41793bca78895868c42ce1536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 13 Jun 2026 12:44:07 -0400 Subject: [PATCH 46/60] fixed clippy lints --- asyncgit/src/graph/buffer.rs | 2 +- asyncgit/src/graph/walker.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/asyncgit/src/graph/buffer.rs b/asyncgit/src/graph/buffer.rs index 3f2e7a47c7..c02354901e 100644 --- a/asyncgit/src/graph/buffer.rs +++ b/asyncgit/src/graph/buffer.rs @@ -206,7 +206,7 @@ impl Buffer { .push(Delta(std::mem::take(&mut self.pending_delta))); let step = self.deltas.len(); - if step % CHECKPOINT_INTERVAL == 0 { + if step.is_multiple_of(CHECKPOINT_INTERVAL) { self.checkpoints.insert(step - 1, self.current.clone()); } } diff --git a/asyncgit/src/graph/walker.rs b/asyncgit/src/graph/walker.rs index d5f7681aad..33c5e2eebb 100644 --- a/asyncgit/src/graph/walker.rs +++ b/asyncgit/src/graph/walker.rs @@ -187,7 +187,7 @@ impl GraphWalker { } /// Number of commits already folded into the graph buffer. - pub fn processed_commits(&self) -> usize { + pub const fn processed_commits(&self) -> usize { self.buffer.deltas.len() } From 6ff65f0dff6c1936e137076247a680da729cd64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 13 Jun 2026 12:57:18 -0400 Subject: [PATCH 47/60] restore [0.28.1] release notes lost in rebase conflict resolution --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d0895e072..528d4e1fd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * when staging the last file in a directory, the first item after the directory is no longer skipped [[@Tillerino](https://github.com/Tillerino)] ([#2748](https://github.com/gitui-org/gitui/issues/2748)) * index-out-of-bounds panic when unstaging lines near the end of a diff ([#2953](https://github.com/gitui-org/gitui/issues/2953)) +## [0.28.1] - 2026-03-21 + +### Changed +* support proper pre-push hook ([#2809](https://github.com/gitui-org/gitui/issues/2809)) +* improve `gitui --version` message [[@hlsxx](https://github.com/hlsxx)] ([#2838](https://github.com/gitui-org/gitui/issues/2838)) +* rust msrv bumped to `1.88` + +### Fixed +* fix extremely slow status loading in large repositories by replacing time-based cache invalidation with generation counter [[@DannyStoll1](https://github.com/DannyStoll1)] ([#2823](https://github.com/gitui-org/gitui/issues/2823)) +* fix panic when renaming or updating remote URL with no remotes configured [[@xvchris](https://github.com/xvchris)] ([#2868](https://github.com/gitui-org/gitui/issues/2868)) + ## [0.28.0] - 2025-12-14 **discard changes on checkout** From 8789a3d31872d743df6092dd6633e999c408a91f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 13 Jun 2026 13:10:12 -0400 Subject: [PATCH 48/60] fixed snapshot regression --- src/gitui.rs | 2 +- .../gitui__gitui__tests__app_log_tab_showing_one_commit.snap | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gitui.rs b/src/gitui.rs index 03d73b11c1..8052218ac2 100644 --- a/src/gitui.rs +++ b/src/gitui.rs @@ -226,7 +226,7 @@ mod tests { // Linux Temp Folder settings.add_filter(r" */tmp/\.tmp\S+-insta/", "[TEMP_FILE]"); // Commit ids that follow a vertical bar - settings.add_filter(r"│[a-z0-9]{7} ", "│[AAAAA] "); + settings.add_filter(r"│[^a-f0-9]*[a-f0-9]{7} ", "│[AAAAA] "); let _bound = settings.bind_to_scope(); } } diff --git a/src/snapshots/gitui__gitui__tests__app_log_tab_showing_one_commit.snap b/src/snapshots/gitui__gitui__tests__app_log_tab_showing_one_commit.snap index bbdd5be8a4..ea57f3165b 100644 --- a/src/snapshots/gitui__gitui__tests__app_log_tab_showing_one_commit.snap +++ b/src/snapshots/gitui__gitui__tests__app_log_tab_showing_one_commit.snap @@ -6,7 +6,7 @@ snapshot_kind: text " Status [1] | Log [2] | Files [3] | Stashing [4] | Stashes [5][TEMP_FILE] " " ──────────────────────────────────────────────────────────────────────────────────────── " "┌Commit 1/1──────────────────────────────────────────────────────────────────────────────┐" -"│[AAAAA] <1m ago name initial █" +"│[AAAAA] <1m ago name initial █" "│ ║" "│ ║" "│ ║" @@ -14,4 +14,4 @@ snapshot_kind: text "│ ║" "│ ║" "└────────────────────────────────────────────────────────────────────────────────────────┘" -"Scroll [↑↓] Mark [˽] Details [⏎] Branches [b] Compare [⇧C] Copy Hash [y] Tag [t] more [.]" +"Scroll [↑↓] Mark [˽] Toggle Graph [g] Details [⏎] Branches [b] Compare [⇧C] more [.]" From 60b252014062c1c43ac37c30ea0a10473558bacf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 13 Jun 2026 14:10:53 -0400 Subject: [PATCH 49/60] fixes to spatial positioning overly pessimistic collapsing rmeoved --- asyncgit/src/graph/buffer.rs | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/asyncgit/src/graph/buffer.rs b/asyncgit/src/graph/buffer.rs index c02354901e..3550c834ce 100644 --- a/asyncgit/src/graph/buffer.rs +++ b/asyncgit/src/graph/buffer.rs @@ -148,16 +148,6 @@ impl Buffer { } fn flush_merge_commits(&mut self) { - // Collect available empty lanes once, before we start maybe filling them. - let mut empty_lanes: Vec = self - .current - .iter() - .enumerate() - .filter_map(|(index, slot)| { - slot.is_none().then_some(index) - }) - .collect(); - while let Some(alias) = self.merge_commits.pop() { // Search for an occupied slot that matches the target alias. // If found, extract its index and a mutable clone of the chunk. @@ -184,14 +174,14 @@ impl Buffer { marker: Markers::Commit, }; - if let Some(empty_index) = empty_lanes.pop() { - self.record_replace(empty_index, Some(new_lane)); - } else { - self.record_insert( - self.current.len(), - Some(new_lane), - ); - } + // Always append the merge's second-parent lane to + // the end instead of reusing an existing empty slot, + // so the new visual column does not collapse + // spatial ordering of lanes already in existence. + self.record_insert( + self.current.len(), + Some(new_lane), + ); } } } From 6d56d48e174683165aa0b7839b6fe71261ff1099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Tue, 23 Jun 2026 16:43:39 -0400 Subject: [PATCH 50/60] newtype wrappers for lane index and alias id --- asyncgit/src/graph/buffer.rs | 9 ++++--- asyncgit/src/graph/chunk.rs | 8 +++--- asyncgit/src/graph/mod.rs | 48 +++++++++++++++++++++++++++--------- asyncgit/src/graph/oids.rs | 9 ++++--- asyncgit/src/graph/walker.rs | 36 +++++++++++++-------------- 5 files changed, 69 insertions(+), 41 deletions(-) diff --git a/asyncgit/src/graph/buffer.rs b/asyncgit/src/graph/buffer.rs index 3550c834ce..402ea24996 100644 --- a/asyncgit/src/graph/buffer.rs +++ b/asyncgit/src/graph/buffer.rs @@ -1,4 +1,5 @@ use super::chunk::{Chunk, Markers}; +use super::AliasId; use std::collections::BTreeMap; /// A single mutation of the lane state, recorded while processing one @@ -39,7 +40,7 @@ pub struct Buffer { /// Aliases of merge commits whose second parent still needs a new /// lane. - merge_commits: Vec, + merge_commits: Vec, /// Scratch list of the [`DeltaOp`]s recorded for processing commit pending_delta: Vec, @@ -64,7 +65,7 @@ impl Buffer { /// Remember `alias` as a merge commit whose second parent must be /// given its own lane. - pub fn track_merge_commit(&mut self, alias: usize) { + pub fn track_merge_commit(&mut self, alias: AliasId) { self.merge_commits.push(alias); } @@ -104,7 +105,7 @@ impl Buffer { fn find_lane_awaiting_parent( &self, - alias: Option, + alias: Option, ) -> Option { let alias = alias?; self.current.iter().position(|slot| { @@ -119,7 +120,7 @@ impl Buffer { fn consume_alias_in_other_chunks( &mut self, - alias: usize, + alias: AliasId, skip_index: usize, ) { for index in 0..self.current.len() { diff --git a/asyncgit/src/graph/chunk.rs b/asyncgit/src/graph/chunk.rs index 8c70795c21..20748330a9 100644 --- a/asyncgit/src/graph/chunk.rs +++ b/asyncgit/src/graph/chunk.rs @@ -1,3 +1,5 @@ +use super::AliasId; + #[derive(Clone, Debug, PartialEq, Eq)] pub enum Markers { Uncommitted, @@ -6,8 +8,8 @@ pub enum Markers { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Chunk { - pub alias: Option, - pub parent_a: Option, - pub parent_b: Option, + pub alias: Option, + pub parent_a: Option, + pub parent_b: Option, pub marker: Markers, } diff --git a/asyncgit/src/graph/mod.rs b/asyncgit/src/graph/mod.rs index d5687ea829..5bc8f87cd2 100644 --- a/asyncgit/src/graph/mod.rs +++ b/asyncgit/src/graph/mod.rs @@ -11,13 +11,39 @@ pub const MAX_LANE_COLORS: usize = 16; // Yes, there are repositories where this is exceeded // Are they very rare? Yes. // On most terminals can more than 256 lanes even be represneted usefully? Not really. -pub type LaneIdx = u8; +#[derive(Clone, Copy, Debug, Default)] +pub struct LaneIndex(u8); -/// Convert a lane position into the compact [`LaneIdx`] -/// representation. This way we can keep full granularity when computing, -/// but not when storing. -pub(crate) fn to_lane_idx(lane: usize) -> LaneIdx { - LaneIdx::try_from(lane).unwrap_or(LaneIdx::MAX) +/// Numeric alias assigned to each commit in the graph. +/// +/// The alias is a dense integer index created by [`GraphOids`](super::oids::GraphOids) +/// that avoids storing full [`CommitId`](crate::sync::CommitId)s inside the lane state. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub struct AliasId(usize); + +impl std::ops::Deref for AliasId { + type Target = usize; + fn deref(&self) -> &usize { + &self.0 + } +} + +impl From for AliasId { + fn from(v: usize) -> Self { + Self(v) + } +} + +impl From for LaneIndex { + fn from(lane: usize) -> Self { + Self(u8::try_from(lane).unwrap_or(u8::MAX)) + } +} + +impl From for usize { + fn from(lane: LaneIndex) -> Self { + lane.0 as usize + } } /// The type of connection between nodes in the graph. @@ -53,10 +79,10 @@ pub enum ConnectionType { #[derive(Clone, Debug, Default)] pub struct GraphRow { /// Number of active lanes at this commit row - pub lane_count: LaneIdx, + pub lane_count: LaneIndex, /// Which lane index this commit sits on - pub commit_lane: LaneIdx, + pub commit_lane: LaneIndex, /// Whether this is a merge commit (two parents) pub is_merge: bool, @@ -70,13 +96,13 @@ pub struct GraphRow { /// Connections emitted per lane: /// None = empty space /// Some((ConnectionType, `color_index`)) = draw this connector in this color - pub lanes: Vec>, + pub lanes: Vec>, /// Horizontal merge bridge: if this commit merges rightward, /// (`from_lane`, `to_lane`) — the span to draw ─ ╭ ╮ across - pub merge_bridge: Option<(LaneIdx, LaneIdx)>, + pub merge_bridge: Option<(LaneIndex, LaneIndex)>, /// Horizontal branch bridges: if this commit spawns branches, /// spans to draw ─ ╭ ╮ across - pub branches: Vec<(LaneIdx, LaneIdx)>, + pub branches: Vec<(LaneIndex, LaneIndex)>, } diff --git a/asyncgit/src/graph/oids.rs b/asyncgit/src/graph/oids.rs index dca161de88..38827c24c9 100644 --- a/asyncgit/src/graph/oids.rs +++ b/asyncgit/src/graph/oids.rs @@ -1,8 +1,9 @@ +use super::AliasId; use crate::sync::CommitId; use std::collections::HashMap; /// mapping of `CommitId` to a numeric alias -pub struct GraphOids(HashMap); +pub struct GraphOids(HashMap); impl Default for GraphOids { fn default() -> Self { @@ -17,18 +18,18 @@ impl GraphOids { } /// Get the alias for `id`, assigning a new one if it doesn't exist yet. - pub fn get_or_insert(&mut self, id: &CommitId) -> usize { + pub fn get_or_insert(&mut self, id: &CommitId) -> AliasId { if let Some(&alias) = self.0.get(id) { return alias; } - let alias = self.0.len(); + let alias = AliasId::from(self.0.len()); self.0.insert(*id, alias); alias } /// Look up the alias for `id`, returning `None` if not found. - pub fn get(&self, id: &CommitId) -> Option { + pub fn get(&self, id: &CommitId) -> Option { self.0.get(id).copied() } } diff --git a/asyncgit/src/graph/walker.rs b/asyncgit/src/graph/walker.rs index 33c5e2eebb..2b46b41233 100644 --- a/asyncgit/src/graph/walker.rs +++ b/asyncgit/src/graph/walker.rs @@ -1,15 +1,13 @@ use super::buffer::Buffer; use super::chunk::{Chunk, Markers}; use super::oids::GraphOids; -use super::{ - to_lane_idx, ConnectionType, GraphRow, LaneIdx, MAX_LANE_COLORS, -}; +use super::{AliasId, ConnectionType, GraphRow, LaneIndex, MAX_LANE_COLORS}; use crate::sync::CommitId; use std::collections::{HashMap, HashSet}; /// Get the lanes color index, which cycles through the ste palette. -fn lane_color(lane: usize) -> LaneIdx { - to_lane_idx(lane % MAX_LANE_COLORS) +fn lane_color(lane: usize) -> LaneIndex { + LaneIndex::from(lane % MAX_LANE_COLORS) } use bitflags::bitflags; @@ -103,9 +101,9 @@ const fn dirs_conn(dirs: Dirs, dotted: bool) -> ConnectionType { /// The line with a vertical component is the chosen way with color /// ensuring lanes stay visually continuous fn overlay_cell( - cell: &mut Option<(ConnectionType, LaneIdx)>, + cell: &mut Option<(ConnectionType, LaneIndex)>, add: Dirs, - color: LaneIdx, + color: LaneIndex, ) { if let Some((conn, existing_color)) = cell { if let Some(existing) = conn_dirs(*conn) { @@ -135,7 +133,7 @@ pub struct GraphWalker { pub branch_lane_map: HashMap, /// Maps a merge commit's alias to the alias of its second parent. - pub merge_parents: HashMap, + pub merge_parents: HashMap, } impl Default for GraphWalker { @@ -236,7 +234,7 @@ impl GraphWalker { } fn draw_merge_bridge( - lanes: &mut [Option<(ConnectionType, LaneIdx)>], + lanes: &mut [Option<(ConnectionType, LaneIndex)>], merge_bridge: Option<(usize, usize)>, commit_lane: usize, current: &[Option], @@ -287,15 +285,15 @@ impl GraphWalker { } fn draw_branching_lanes( - lanes: &mut Vec>, + lanes: &mut Vec>, branching_lanes: &[usize], commit_lane: usize, - ) -> Vec<(LaneIdx, LaneIdx)> { + ) -> Vec<(LaneIndex, LaneIndex)> { let mut branches = Vec::new(); for &branch_lane in branching_lanes { let from = std::cmp::min(branch_lane, commit_lane); let to = std::cmp::max(branch_lane, commit_lane); - branches.push((to_lane_idx(from), to_lane_idx(to))); + branches.push((LaneIndex::from(from), LaneIndex::from(to))); if lanes.len() <= to { lanes.resize(to + 1, None); @@ -403,14 +401,14 @@ impl GraphWalker { ); GraphRow { - lane_count: to_lane_idx(current.iter().flatten().count()), - commit_lane: to_lane_idx(commit_lane), + lane_count: LaneIndex::from(current.iter().flatten().count()), + commit_lane: LaneIndex::from(commit_lane), is_merge, is_branch_tip, is_stash, lanes, merge_bridge: merge_bridge - .map(|(f, t)| (to_lane_idx(f), to_lane_idx(t))), + .map(|(f, t)| (LaneIndex::from(f), LaneIndex::from(t))), branches, } } @@ -418,9 +416,9 @@ impl GraphWalker { #[allow(clippy::too_many_arguments)] fn fill_lanes( &self, - lanes: &mut [Option<(ConnectionType, LaneIdx)>], + lanes: &mut [Option<(ConnectionType, LaneIndex)>], curr: &[Option], - alias: Option, + alias: Option, head_id: Option<&CommitId>, is_stash: bool, is_merge: bool, @@ -473,10 +471,10 @@ impl GraphWalker { /// between its two ends, merging with whatever each cell already /// shows. fn draw_bridge_span( - lanes: &mut [Option<(ConnectionType, LaneIdx)>], + lanes: &mut [Option<(ConnectionType, LaneIndex)>], from: usize, to: usize, - color: LaneIdx, + color: LaneIndex, ) { for lane in lanes.iter_mut().take(to).skip(from + 1) { overlay_cell(lane, Dirs::LEFT | Dirs::RIGHT, color); From 50912bbc6b882737b7581e6158beed081abb5903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Tue, 23 Jun 2026 16:43:58 -0400 Subject: [PATCH 51/60] rename Dirs to Directions --- asyncgit/src/graph/walker.rs | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/asyncgit/src/graph/walker.rs b/asyncgit/src/graph/walker.rs index 2b46b41233..e78462062e 100644 --- a/asyncgit/src/graph/walker.rs +++ b/asyncgit/src/graph/walker.rs @@ -17,7 +17,7 @@ bitflags! { /// are merged through this representation rather than /// a naive overwrite for readability. #[derive(Clone, Copy, Default)] - struct Dirs: u8 { + struct Directions: u8 { const UP = 0b0001; const DOWN = 0b0010; const LEFT = 0b0100; @@ -25,7 +25,7 @@ bitflags! { } } -impl Dirs { +impl Directions { #[allow(clippy::missing_const_for_fn)] fn merge(self, other: Self) -> Self { Self::from_bits_retain(self.bits() | other.bits()) @@ -39,23 +39,23 @@ impl Dirs { /// The sub network of an existing connection glyph /// `None` represents commit markers, which are never drawn over. -fn conn_dirs(conn: ConnectionType) -> Option { +fn conn_dirs(conn: ConnectionType) -> Option { Some(match conn { ConnectionType::Vertical | ConnectionType::VerticalDotted => { - Dirs::UP | Dirs::DOWN + Directions::UP | Directions::DOWN } - ConnectionType::MergeBridgeMid => Dirs::LEFT | Dirs::RIGHT, - ConnectionType::MergeBridgeStart => Dirs::DOWN | Dirs::LEFT, - ConnectionType::MergeBridgeEnd => Dirs::DOWN | Dirs::RIGHT, - ConnectionType::BranchUp => Dirs::UP | Dirs::LEFT, - ConnectionType::BranchUpRight => Dirs::UP | Dirs::RIGHT, - ConnectionType::TeeLeft => Dirs::UP | Dirs::DOWN | Dirs::LEFT, + ConnectionType::MergeBridgeMid => Directions::LEFT | Directions::RIGHT, + ConnectionType::MergeBridgeStart => Directions::DOWN | Directions::LEFT, + ConnectionType::MergeBridgeEnd => Directions::DOWN | Directions::RIGHT, + ConnectionType::BranchUp => Directions::UP | Directions::LEFT, + ConnectionType::BranchUpRight => Directions::UP | Directions::RIGHT, + ConnectionType::TeeLeft => Directions::UP | Directions::DOWN | Directions::LEFT, ConnectionType::TeeRight => { - Dirs::UP | Dirs::DOWN | Dirs::RIGHT + Directions::UP | Directions::DOWN | Directions::RIGHT } - ConnectionType::TeeUp => Dirs::UP | Dirs::LEFT | Dirs::RIGHT, + ConnectionType::TeeUp => Directions::UP | Directions::LEFT | Directions::RIGHT, ConnectionType::TeeDown => { - Dirs::DOWN | Dirs::LEFT | Dirs::RIGHT + Directions::DOWN | Directions::LEFT | Directions::RIGHT } ConnectionType::CommitNormal | ConnectionType::CommitBranch @@ -69,11 +69,11 @@ fn conn_dirs(conn: ConnectionType) -> Option { /// Vertical lines take precedence in crossed cells. /// Yet the horizontal bridge continues in /// the spacer columns either side, so we retain wholeness. -const fn dirs_conn(dirs: Dirs, dotted: bool) -> ConnectionType { - let up = dirs.contains(Dirs::UP); - let down = dirs.contains(Dirs::DOWN); - let left = dirs.contains(Dirs::LEFT); - let right = dirs.contains(Dirs::RIGHT); +const fn dirs_conn(dirs: Directions, dotted: bool) -> ConnectionType { + let up = dirs.contains(Directions::UP); + let down = dirs.contains(Directions::DOWN); + let left = dirs.contains(Directions::LEFT); + let right = dirs.contains(Directions::RIGHT); match (up, down, left, right) { (true, true, true, false) => ConnectionType::TeeLeft, (true, true, false, true) => ConnectionType::TeeRight, @@ -102,7 +102,7 @@ const fn dirs_conn(dirs: Dirs, dotted: bool) -> ConnectionType { /// ensuring lanes stay visually continuous fn overlay_cell( cell: &mut Option<(ConnectionType, LaneIndex)>, - add: Dirs, + add: Directions, color: LaneIndex, ) { if let Some((conn, existing_color)) = cell { @@ -258,15 +258,15 @@ impl GraphWalker { // target lane with the precise corner/junction lanes[target_lane] = None; let target_dirs = { - let mut d = Dirs::DOWN; + let mut d = Directions::DOWN; if continues_up { - d |= Dirs::UP; + d |= Directions::UP; } if target_lane > commit_lane { - d |= Dirs::LEFT; + d |= Directions::LEFT; } if target_lane < commit_lane { - d |= Dirs::RIGHT; + d |= Directions::RIGHT; } d }; @@ -306,12 +306,12 @@ impl GraphWalker { lane_color(branch_lane), ); let branch_dirs = { - let mut d = Dirs::UP; + let mut d = Directions::UP; if branch_lane == to { - d |= Dirs::LEFT; + d |= Directions::LEFT; } if branch_lane == from { - d |= Dirs::RIGHT; + d |= Directions::RIGHT; } d }; @@ -477,7 +477,7 @@ impl GraphWalker { color: LaneIndex, ) { for lane in lanes.iter_mut().take(to).skip(from + 1) { - overlay_cell(lane, Dirs::LEFT | Dirs::RIGHT, color); + overlay_cell(lane, Directions::LEFT | Directions::RIGHT, color); } } } From a7e24395fe3bed301cbd0241977125d2f7456291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Tue, 23 Jun 2026 16:45:07 -0400 Subject: [PATCH 52/60] extract rendering helpers from walker --- asyncgit/src/graph/mod.rs | 2 +- asyncgit/src/graph/walker.rs | 552 ++++++++++++++++++++++------------- 2 files changed, 354 insertions(+), 200 deletions(-) diff --git a/asyncgit/src/graph/mod.rs b/asyncgit/src/graph/mod.rs index 5bc8f87cd2..185962768e 100644 --- a/asyncgit/src/graph/mod.rs +++ b/asyncgit/src/graph/mod.rs @@ -11,7 +11,7 @@ pub const MAX_LANE_COLORS: usize = 16; // Yes, there are repositories where this is exceeded // Are they very rare? Yes. // On most terminals can more than 256 lanes even be represneted usefully? Not really. -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct LaneIndex(u8); /// Numeric alias assigned to each commit in the graph. diff --git a/asyncgit/src/graph/walker.rs b/asyncgit/src/graph/walker.rs index e78462062e..01fca55b4c 100644 --- a/asyncgit/src/graph/walker.rs +++ b/asyncgit/src/graph/walker.rs @@ -3,6 +3,7 @@ use super::chunk::{Chunk, Markers}; use super::oids::GraphOids; use super::{AliasId, ConnectionType, GraphRow, LaneIndex, MAX_LANE_COLORS}; use crate::sync::CommitId; +use core::cmp::Ordering; use std::collections::{HashMap, HashSet}; /// Get the lanes color index, which cycles through the ste palette. @@ -44,16 +45,28 @@ fn conn_dirs(conn: ConnectionType) -> Option { ConnectionType::Vertical | ConnectionType::VerticalDotted => { Directions::UP | Directions::DOWN } - ConnectionType::MergeBridgeMid => Directions::LEFT | Directions::RIGHT, - ConnectionType::MergeBridgeStart => Directions::DOWN | Directions::LEFT, - ConnectionType::MergeBridgeEnd => Directions::DOWN | Directions::RIGHT, + ConnectionType::MergeBridgeMid => { + Directions::LEFT | Directions::RIGHT + } + ConnectionType::MergeBridgeStart => { + Directions::DOWN | Directions::LEFT + } + ConnectionType::MergeBridgeEnd => { + Directions::DOWN | Directions::RIGHT + } ConnectionType::BranchUp => Directions::UP | Directions::LEFT, - ConnectionType::BranchUpRight => Directions::UP | Directions::RIGHT, - ConnectionType::TeeLeft => Directions::UP | Directions::DOWN | Directions::LEFT, + ConnectionType::BranchUpRight => { + Directions::UP | Directions::RIGHT + } + ConnectionType::TeeLeft => { + Directions::UP | Directions::DOWN | Directions::LEFT + } ConnectionType::TeeRight => { Directions::UP | Directions::DOWN | Directions::RIGHT } - ConnectionType::TeeUp => Directions::UP | Directions::LEFT | Directions::RIGHT, + ConnectionType::TeeUp => { + Directions::UP | Directions::LEFT | Directions::RIGHT + } ConnectionType::TeeDown => { Directions::DOWN | Directions::LEFT | Directions::RIGHT } @@ -152,33 +165,36 @@ impl GraphWalker { } } - pub fn process(&mut self, id: CommitId, parents: &[CommitId]) { - let alias = self.oids.get_or_insert(&id); + pub fn process( + &mut self, + commit_id: CommitId, + parents: &[CommitId], + ) { + let commit_alias = self.oids.get_or_insert(&commit_id); - let mut mapped_parents = - parents.iter().map(|p| self.oids.get_or_insert(p)); + let mut mapped_parents = parents + .iter() + .map(|parent_id| self.oids.get_or_insert(parent_id)); - // We make the executive and saddening decision to not support octo/mega merges - // TUIs are simply a backwards medium for representing this complexity + // We explicitly cap support at 2 parents, ignoring octo/mega merges. let first_parent = mapped_parents.next(); let second_parent = mapped_parents.next(); let chunk = Chunk { - alias: Some(alias), + alias: Some(commit_alias), parent_a: first_parent, parent_b: second_parent, marker: Markers::Commit, }; - second_parent.map(|b| self.merge_parents.insert(alias, b)); + if let Some(second) = second_parent { + self.merge_parents.insert(commit_alias, second); - if first_parent.is_some() - && second_parent.is_some() - && !self.buffer.current.iter().flatten().any(|commit| { - commit.parent_a == second_parent - && commit.parent_b.is_none() - }) { - self.buffer.track_merge_commit(alias); + if first_parent.is_some() + && !self.is_redundant_merge_track(second) + { + self.buffer.track_merge_commit(commit_alias); + } } self.buffer.update(&chunk); @@ -192,7 +208,7 @@ impl GraphWalker { pub fn compute_rows( &self, commit_range: &[CommitId], - global_start: usize, + global_start_index: usize, branch_tips: &HashSet, stashes: &HashSet, head_id: Option<&CommitId>, @@ -201,30 +217,36 @@ impl GraphWalker { return Vec::new(); } - // decompress one row before the range (when there is one) so - // every row's predecessor state comes from the same replay - let snap_start = global_start.saturating_sub(1); - let end = global_start + commit_range.len() - 1; - let snapshots = self.buffer.decompress(snap_start, end); - let offset = global_start - snap_start; + // Decompress one row before the range to establish predecessor state + let snapshot_start_index = + global_start_index.saturating_sub(1); + let snapshot_end_index = + global_start_index + commit_range.len() - 1; + let snapshots = self + .buffer + .decompress(snapshot_start_index, snapshot_end_index); + let index_offset = global_start_index - snapshot_start_index; commit_range .iter() .enumerate() - .map(|(index, commit_id)| { - let place = index + offset; + .map(|(range_index, commit_id)| { + let snapshot_index = range_index + index_offset; + + let current_snapshot = snapshots + .get(snapshot_index) + .map(Vec::as_slice) + .unwrap_or_default(); - let current: &[Option] = - snapshots.get(place).map_or(&[], Vec::as_slice); - let previous = place + let previous_snapshot = snapshot_index .checked_sub(1) - .and_then(|i| snapshots.get(i)) + .and_then(|index| snapshots.get(index)) .map(Vec::as_slice); self.render_row( commit_id, - current, - previous, + current_snapshot, + previous_snapshot, branch_tips, stashes, head_id, @@ -237,50 +259,48 @@ impl GraphWalker { lanes: &mut [Option<(ConnectionType, LaneIndex)>], merge_bridge: Option<(usize, usize)>, commit_lane: usize, - current: &[Option], - previous: Option<&[Option]>, + current_snapshot: &[Option], + previous_snapshot: Option<&[Option]>, ) { - let Some((from, to)) = merge_bridge.filter(|(f, t)| f != t) - else { + let Some((source_lane, target_lane)) = merge_bridge else { return; }; - let target_lane = if from == commit_lane { to } else { from }; - - // only draw the corner continuing upward when the target - // lane already existed on the previous row; a brand-new - // lane starts at this corner - let continues_up = current[target_lane].is_some() - && previous.is_some_and(|prev| { - prev.get(target_lane) == Some(¤t[target_lane]) - }); + if source_lane == target_lane { + return; + } - // replace the plain vertical fill_lanes drew for the - // target lane with the precise corner/junction - lanes[target_lane] = None; - let target_dirs = { - let mut d = Directions::DOWN; - if continues_up { - d |= Directions::UP; - } - if target_lane > commit_lane { - d |= Directions::LEFT; - } - if target_lane < commit_lane { - d |= Directions::RIGHT; - } - d + let destination_lane = if source_lane == commit_lane { + target_lane + } else { + source_lane }; + let connection_color = lane_color(destination_lane); + + let continues_upwards = Self::lane_continues_upwards( + destination_lane, + current_snapshot, + previous_snapshot, + ); + + let target_directions = Self::calculate_merge_directions( + commit_lane, + destination_lane, + continues_upwards, + ); + + // Replace the plain vertical fill with the precise corner/junction + lanes[destination_lane] = None; overlay_cell( - &mut lanes[target_lane], - target_dirs, - lane_color(target_lane), + &mut lanes[destination_lane], + target_directions, + connection_color, ); Self::draw_bridge_span( lanes, - from, - to, - lane_color(target_lane), + source_lane, + target_lane, + connection_color, ); } @@ -289,109 +309,188 @@ impl GraphWalker { branching_lanes: &[usize], commit_lane: usize, ) -> Vec<(LaneIndex, LaneIndex)> { - let mut branches = Vec::new(); - for &branch_lane in branching_lanes { - let from = std::cmp::min(branch_lane, commit_lane); - let to = std::cmp::max(branch_lane, commit_lane); - branches.push((LaneIndex::from(from), LaneIndex::from(to))); - - if lanes.len() <= to { - lanes.resize(to + 1, None); - } + branching_lanes + .iter() + .map(|&branch_lane| { + let start_lane = + std::cmp::min(branch_lane, commit_lane); + let end_lane = + std::cmp::max(branch_lane, commit_lane); + + Self::ensure_lane_capacity(lanes, end_lane); + + let connection_color = lane_color(branch_lane); + Self::draw_bridge_span( + lanes, + start_lane, + end_lane, + connection_color, + ); + + let branch_directions = + Self::calculate_branch_directions( + branch_lane, + start_lane, + end_lane, + ); + overlay_cell( + &mut lanes[branch_lane], + branch_directions, + connection_color, + ); + + (LaneIndex::from(start_lane), LaneIndex::from(end_lane)) + }) + .collect() + } - Self::draw_bridge_span( - lanes, - from, - to, - lane_color(branch_lane), - ); - let branch_dirs = { - let mut d = Directions::UP; - if branch_lane == to { - d |= Directions::LEFT; - } - if branch_lane == from { - d |= Directions::RIGHT; - } - d - }; - overlay_cell( - &mut lanes[branch_lane], - branch_dirs, - lane_color(branch_lane), - ); + /// Checks if tracking a merge commit would be redundant based on current buffer state. + fn is_redundant_merge_track( + &self, + target_parent: AliasId, + ) -> bool { + self.buffer.current.iter().flatten().any(|commit| { + commit.parent_a == Some(target_parent) + && commit.parent_b.is_none() + }) + } + + /// Determines if a lane should draw an upward-connecting corner. + fn lane_continues_upwards( + target_lane: usize, + current_snapshot: &[Option], + previous_snapshot: Option<&[Option]>, + ) -> bool { + let exists_in_current = + current_snapshot.get(target_lane).is_some(); + let matches_previous = + previous_snapshot.is_some_and(|previous| { + previous.get(target_lane) + == current_snapshot.get(target_lane) + }); + + exists_in_current && matches_previous + } + + /// Uses `Ordering` to elegantly map spatial relationships to visual bitmasks. + fn calculate_merge_directions( + commit_lane: usize, + target_lane: usize, + continues_upwards: bool, + ) -> Directions { + let mut directions = Directions::DOWN; + + if continues_upwards { + directions |= Directions::UP; + } + + match target_lane.cmp(&commit_lane) { + Ordering::Greater => directions |= Directions::LEFT, + Ordering::Less => directions |= Directions::RIGHT, + Ordering::Equal => {} + } + + directions + } + + fn calculate_branch_directions( + branch_lane: usize, + start_lane: usize, + end_lane: usize, + ) -> Directions { + let mut directions = Directions::UP; + + if branch_lane == end_lane { + directions |= Directions::LEFT; + } + if branch_lane == start_lane { + directions |= Directions::RIGHT; + } + + directions + } + + fn ensure_lane_capacity( + lanes: &mut Vec>, + required_index: usize, + ) { + if lanes.len() <= required_index { + lanes.resize(required_index + 1, None); } - branches } - fn render_row( + pub fn render_row( &self, commit_id: &CommitId, - current: &[Option], - previous: Option<&[Option]>, + current_snapshot: &[Option], + previous_snapshot: Option<&[Option]>, branch_tips: &HashSet, stashes: &HashSet, head_id: Option<&CommitId>, ) -> GraphRow { - let alias = self.oids.get(commit_id); - let commit_lane = current - .iter() - .position(|c| { - c.as_ref().is_some_and(|chunk| { - alias.is_some() && chunk.alias == alias - }) - }) - .unwrap_or(0); - - let parent_b_alias = - alias.and_then(|a| self.merge_parents.get(&a).copied()); - - let is_merge = parent_b_alias.is_some(); + let commit_alias = self.oids.get(commit_id); + let head_alias = head_id.and_then(|id| self.oids.get(id)); + let second_parent_alias = commit_alias.and_then(|alias| { + self.merge_parents.get(&alias).copied() + }); + + let commit_lane = + Self::find_commit_lane(current_snapshot, commit_alias); + let is_merge = second_parent_alias.is_some(); let is_branch_tip = branch_tips.contains(commit_id); let is_stash = stashes.contains(commit_id); - let branching_lanes: Vec = previous - .into_iter() - .flatten() // Unwrapping the optional, returning empty vec when None - .enumerate() - .filter(|(i, pc)| { - pc.is_some() - && current.get(*i).is_none_or(Option::is_none) - }) - .map(|(i, _)| i) - .collect(); + let branching_lanes = Self::find_branching_lanes( + current_snapshot, + previous_snapshot, + ); - let mut lanes = vec![None; current.len()]; + let merge_bridge = + second_parent_alias.and_then(|parent_alias| { + Self::calculate_merge_bridge( + current_snapshot, + commit_lane, + parent_alias, + ) + }); - let merge_bridge = if is_merge && parent_b_alias.is_some() { - current + let mut lanes: Vec> = + current_snapshot .iter() - .position(|c| { - c.as_ref().is_some_and(|chunk| { - chunk.parent_a == parent_b_alias + .enumerate() + .map(|(lane_index, chunk_option)| { + let chunk = chunk_option.as_ref()?; // Returns None early if the chunk is missing + + if commit_alias.is_some() + && chunk.alias == commit_alias + { + let connection = + Self::determine_commit_connection( + is_stash, + is_merge, + is_branch_tip, + ); + return Some(( + connection, + lane_color(lane_index), + )); + } + + Self::determine_passthrough_connection( + chunk, lane_index, head_alias, + ) + .map(|connection| { + (connection, lane_color(lane_index)) }) }) - .map(|t| (commit_lane.min(t), commit_lane.max(t))) - } else { - None - }; - - self.fill_lanes( - &mut lanes, - current, - alias, - head_id, - is_stash, - is_merge, - is_branch_tip, - ); + .collect(); Self::draw_merge_bridge( &mut lanes, merge_bridge, commit_lane, - current, - previous, + current_snapshot, + previous_snapshot, ); let branches = Self::draw_branching_lanes( @@ -400,70 +499,121 @@ impl GraphWalker { commit_lane, ); + let active_lane_count = + current_snapshot.iter().flatten().count(); + GraphRow { - lane_count: LaneIndex::from(current.iter().flatten().count()), + lane_count: LaneIndex::from(active_lane_count), commit_lane: LaneIndex::from(commit_lane), is_merge, is_branch_tip, is_stash, lanes, - merge_bridge: merge_bridge - .map(|(f, t)| (LaneIndex::from(f), LaneIndex::from(t))), + merge_bridge: merge_bridge.map(|(source, target)| { + (LaneIndex::from(source), LaneIndex::from(target)) + }), branches, } } - #[allow(clippy::too_many_arguments)] - fn fill_lanes( - &self, - lanes: &mut [Option<(ConnectionType, LaneIndex)>], - curr: &[Option], - alias: Option, - head_id: Option<&CommitId>, + /// Locates the primary lane for the current commit. + fn find_commit_lane( + current_snapshot: &[Option], + commit_alias: Option, + ) -> usize { + let Some(target_alias) = commit_alias else { + return 0; + }; + + current_snapshot + .iter() + .position(|chunk_option| { + chunk_option.as_ref().is_some_and(|chunk| { + chunk.alias == Some(target_alias) + }) + }) + .unwrap_or(0) + } + + /// Computes the span (min, max) between the commit's lane and its second parent's lane. + fn calculate_merge_bridge( + current_snapshot: &[Option], + commit_lane: usize, + second_parent_alias: AliasId, + ) -> Option<(usize, usize)> { + current_snapshot + .iter() + .position(|chunk_option| { + chunk_option.as_ref().is_some_and(|chunk| { + chunk.parent_a == Some(second_parent_alias) + }) + }) + .map(|target_lane| { + ( + commit_lane.min(target_lane), + commit_lane.max(target_lane), + ) + }) + } + + /// Identifies lanes that existed in the previous row but terminated before the current row. + fn find_branching_lanes( + current_snapshot: &[Option], + previous_snapshot: Option<&[Option]>, + ) -> Vec { + let Some(previous) = previous_snapshot else { + return Vec::new(); + }; + + previous + .iter() + .enumerate() + .filter(|(index, previous_chunk)| { + previous_chunk.is_some() + && current_snapshot + .get(*index) + .is_none_or(Option::is_none) + }) + .map(|(index, _)| index) + .collect() + } + + /// Determines the correct node type for the active commit lane. + fn determine_commit_connection( is_stash: bool, is_merge: bool, is_branch_tip: bool, - ) { - for (lane_idx, chunk_item) in curr.iter().enumerate() { - let Some(chunk) = chunk_item.as_ref() else { - continue; - }; - - if alias.is_some() && chunk.alias == alias { - let conn_type = - match (is_stash, is_merge, is_branch_tip) { - (true, _, _) => ConnectionType::CommitStash, - (_, true, _) => ConnectionType::CommitMerge, - (_, _, true) => ConnectionType::CommitBranch, - _ => ConnectionType::CommitNormal, - }; - - lanes[lane_idx] = - Some((conn_type, lane_color(lane_idx))); - } else { - let target_oid = - head_id.and_then(|h| self.oids.get(h)); - - let is_dotted = lane_idx == 0 - && target_oid.is_some() - && (chunk.parent_a == target_oid - || chunk.parent_b == target_oid); - - let is_orphan = chunk.parent_a.is_none() - && chunk.parent_b.is_none(); + ) -> ConnectionType { + match (is_stash, is_merge, is_branch_tip) { + (true, _, _) => ConnectionType::CommitStash, + (_, true, _) => ConnectionType::CommitMerge, + (_, _, true) => ConnectionType::CommitBranch, + _ => ConnectionType::CommitNormal, + } + } - if is_orphan { - continue; - } + /// Determines the correct vertical line style for non-commit passthrough lanes. + fn determine_passthrough_connection( + chunk: &Chunk, + lane_index: usize, + head_alias: Option, + ) -> Option { + let is_orphan = + chunk.parent_a.is_none() && chunk.parent_b.is_none(); + + if is_orphan { + return None; + } - let conn = if is_dotted { - ConnectionType::VerticalDotted - } else { - ConnectionType::Vertical - }; + let is_dotted = lane_index == 0 + && head_alias.is_some() + && (chunk.parent_a == head_alias + || chunk.parent_b == head_alias); - lanes[lane_idx] = Some((conn, lane_color(lane_idx))); - } + if is_dotted { + Some(ConnectionType::VerticalDotted) + } else { + Some(ConnectionType::Vertical) } } @@ -477,7 +627,11 @@ impl GraphWalker { color: LaneIndex, ) { for lane in lanes.iter_mut().take(to).skip(from + 1) { - overlay_cell(lane, Directions::LEFT | Directions::RIGHT, color); + overlay_cell( + lane, + Directions::LEFT | Directions::RIGHT, + color, + ); } } } From 5cf86c9b2ad21a28d2154fec2d517772f92a8314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Tue, 23 Jun 2026 16:50:21 -0400 Subject: [PATCH 53/60] clippy fixes --- asyncgit/src/graph/mod.rs | 2 +- asyncgit/src/graph/walker.rs | 11 ++++++++--- src/components/commitlist.rs | 9 ++++++--- src/main.rs | 1 - 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/asyncgit/src/graph/mod.rs b/asyncgit/src/graph/mod.rs index 185962768e..a69e89c2a7 100644 --- a/asyncgit/src/graph/mod.rs +++ b/asyncgit/src/graph/mod.rs @@ -42,7 +42,7 @@ impl From for LaneIndex { impl From for usize { fn from(lane: LaneIndex) -> Self { - lane.0 as usize + lane.0 as Self } } diff --git a/asyncgit/src/graph/walker.rs b/asyncgit/src/graph/walker.rs index 01fca55b4c..d64774b1af 100644 --- a/asyncgit/src/graph/walker.rs +++ b/asyncgit/src/graph/walker.rs @@ -1,7 +1,9 @@ use super::buffer::Buffer; use super::chunk::{Chunk, Markers}; use super::oids::GraphOids; -use super::{AliasId, ConnectionType, GraphRow, LaneIndex, MAX_LANE_COLORS}; +use super::{ + AliasId, ConnectionType, GraphRow, LaneIndex, MAX_LANE_COLORS, +}; use crate::sync::CommitId; use core::cmp::Ordering; use std::collections::{HashMap, HashSet}; @@ -339,7 +341,10 @@ impl GraphWalker { connection_color, ); - (LaneIndex::from(start_lane), LaneIndex::from(end_lane)) + ( + LaneIndex::from(start_lane), + LaneIndex::from(end_lane), + ) }) .collect() } @@ -579,7 +584,7 @@ impl GraphWalker { } /// Determines the correct node type for the active commit lane. - fn determine_commit_connection( + const fn determine_commit_connection( is_stash: bool, is_merge: bool, is_branch_tip: bool, diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 5d84400c7f..2b524567cf 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -1354,12 +1354,15 @@ mod tests { #[test] fn test_build_graph_spans() { let row = GraphRow { - lane_count: 1, - commit_lane: 0, + lane_count: 1.into(), + commit_lane: 0.into(), is_merge: false, is_branch_tip: false, is_stash: false, - lanes: vec![Some((ConnectionType::CommitNormal, 0))], + lanes: vec![Some(( + ConnectionType::CommitNormal, + 0.into(), + ))], merge_bridge: None, branches: vec![], }; diff --git a/src/main.rs b/src/main.rs index cb5fc69be6..db8b1dbf18 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,6 @@ #![forbid(unsafe_code)] #![deny( - mismatched_lifetime_syntaxes, unused_imports, unused_must_use, dead_code, From b615e9c3e29b6f5ca91220e16e8df37ab5ee1a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 9 Jul 2026 13:27:17 -0400 Subject: [PATCH 54/60] rename AliasId to CommitAlias, add UnwalkedAlias --- asyncgit/src/graph/mod.rs | 18 +++++++++++++++--- asyncgit/src/graph/oids.rs | 19 +++++++------------ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/asyncgit/src/graph/mod.rs b/asyncgit/src/graph/mod.rs index a69e89c2a7..36519fe818 100644 --- a/asyncgit/src/graph/mod.rs +++ b/asyncgit/src/graph/mod.rs @@ -19,16 +19,28 @@ pub struct LaneIndex(u8); /// The alias is a dense integer index created by [`GraphOids`](super::oids::GraphOids) /// that avoids storing full [`CommitId`](crate::sync::CommitId)s inside the lane state. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -pub struct AliasId(usize); +pub struct CommitAlias(usize); -impl std::ops::Deref for AliasId { +/// An alias for a commit that had not yet been walked when the value +/// was minted. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct UnwalkedAlias(CommitAlias); + +impl UnwalkedAlias { + /// The underlying alias, for comparisons against walked commits. + pub const fn get(self) -> CommitAlias { + self.0 + } +} + +impl std::ops::Deref for CommitAlias { type Target = usize; fn deref(&self) -> &usize { &self.0 } } -impl From for AliasId { +impl From for CommitAlias { fn from(v: usize) -> Self { Self(v) } diff --git a/asyncgit/src/graph/oids.rs b/asyncgit/src/graph/oids.rs index 38827c24c9..0ee8591532 100644 --- a/asyncgit/src/graph/oids.rs +++ b/asyncgit/src/graph/oids.rs @@ -1,35 +1,30 @@ -use super::AliasId; +use super::CommitAlias; use crate::sync::CommitId; use std::collections::HashMap; /// mapping of `CommitId` to a numeric alias -pub struct GraphOids(HashMap); - -impl Default for GraphOids { - fn default() -> Self { - Self::new() - } -} +#[derive(Default)] +pub struct GraphOids(HashMap); impl GraphOids { /// Create an empty alias map. pub fn new() -> Self { - Self(HashMap::new()) + Self::default() } /// Get the alias for `id`, assigning a new one if it doesn't exist yet. - pub fn get_or_insert(&mut self, id: &CommitId) -> AliasId { + pub fn get_or_insert(&mut self, id: &CommitId) -> CommitAlias { if let Some(&alias) = self.0.get(id) { return alias; } - let alias = AliasId::from(self.0.len()); + let alias = CommitAlias::from(self.0.len()); self.0.insert(*id, alias); alias } /// Look up the alias for `id`, returning `None` if not found. - pub fn get(&self, id: &CommitId) -> Option { + pub fn get(&self, id: &CommitId) -> Option { self.0.get(id).copied() } } From d4d0c2e0b897951304fc24810d3946cd7ece9ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 9 Jul 2026 13:27:21 -0400 Subject: [PATCH 55/60] replace Chunk/Markers with LaneSlot enum --- asyncgit/src/graph/chunk.rs | 66 +++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/asyncgit/src/graph/chunk.rs b/asyncgit/src/graph/chunk.rs index 20748330a9..e186047b60 100644 --- a/asyncgit/src/graph/chunk.rs +++ b/asyncgit/src/graph/chunk.rs @@ -1,15 +1,61 @@ -use super::AliasId; +use super::{CommitAlias, UnwalkedAlias}; +/// A lane's occupant at one point in the walk. #[derive(Clone, Debug, PartialEq, Eq)] -pub enum Markers { - Uncommitted, - Commit, +pub enum LaneSlot { + /// A walked commit flowing down to its first parent. + Flowing { + alias: CommitAlias, + parent: UnwalkedAlias, + }, + + /// A flowing lane that still owes a bridge to a second parent + FlowingMerge { + alias: CommitAlias, + parent: UnwalkedAlias, + second: UnwalkedAlias, + }, + + /// A root commit: draws its node, nothing continues below. + Settled { alias: CommitAlias }, + + /// Reserved for a merge's second parent that hasn't been reached + /// by the walk yet. Once that parent is processed, the + /// reservation is replaced by the parent's own slot. + Reserved { parent: UnwalkedAlias }, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Chunk { - pub alias: Option, - pub parent_a: Option, - pub parent_b: Option, - pub marker: Markers, +impl LaneSlot { + /// The commit this lane currently belongs to, `None` for a + /// reserved placeholder. + pub const fn alias(&self) -> Option { + match self { + Self::Flowing { alias, .. } + | Self::FlowingMerge { alias, .. } + | Self::Settled { alias } => Some(*alias), + Self::Reserved { .. } => None, + } + } + + /// The parent whose placement this lane waits on; the lane stays + /// open (drawing a vertical line) until that commit is walked. + pub const fn awaits(&self) -> Option { + match self { + Self::Flowing { parent, .. } + | Self::FlowingMerge { parent, .. } + | Self::Reserved { parent } => Some(parent.get()), + Self::Settled { .. } => None, + } + } + + /// The pending second parent of a merge that hasn't been given + /// its own lane. + pub const fn second(&self) -> Option { + match self { + Self::FlowingMerge { second, .. } => Some(second.get()), + Self::Flowing { .. } + | Self::Settled { .. } + | Self::Reserved { .. } => None, + } + } } From 11842d3fc46b9eaf4b146b3676f299aacbf59014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 9 Jul 2026 13:27:25 -0400 Subject: [PATCH 56/60] refactor buffer for LaneSlot --- asyncgit/src/graph/buffer.rs | 163 +++++++++++++++++++++-------------- 1 file changed, 99 insertions(+), 64 deletions(-) diff --git a/asyncgit/src/graph/buffer.rs b/asyncgit/src/graph/buffer.rs index 402ea24996..0278802b65 100644 --- a/asyncgit/src/graph/buffer.rs +++ b/asyncgit/src/graph/buffer.rs @@ -1,14 +1,22 @@ -use super::chunk::{Chunk, Markers}; -use super::AliasId; +use super::chunk::LaneSlot; +use super::CommitAlias; use std::collections::BTreeMap; /// A single mutation of the lane state, recorded while processing one /// commit. #[derive(Clone, Debug)] pub enum DeltaOp { - Insert { index: usize, item: Option }, - Remove { index: usize }, - Replace { index: usize, new: Option }, + Insert { + index: usize, + item: Option, + }, + Remove { + index: usize, + }, + Replace { + index: usize, + new: Option, + }, } /// All lane-state mutations caused by processing a single commit. @@ -22,25 +30,25 @@ const CHECKPOINT_INTERVAL: usize = 100; /// Delta-compressed history of the graph's lane state. /// /// While walking the log top-down, every commit mutates the set of -/// active lanes (a `Vec>`, one slot per lane). So, storign a +/// active lanes (a `Vec>`, one slot per lane). So, storign a /// full copy of that state for each commit is a waste. This /// buffer preserves ONLY the latest state PLUS the list of [`Delta`]s that /// produced it. /// Use [`Buffer::decompress`] to get the complete version. pub struct Buffer { /// Lane state after the most recently processed commit. - pub current: Vec>, + pub current: Vec>, /// One [`Delta`] per processed commit, in the order of the walk. pub deltas: Vec, /// Full lane-state snapshots taken every `CHECKPOINT_INTERVAL` /// commits, keyed by delta index, for reducing decompression cost. - pub checkpoints: BTreeMap>>, + pub checkpoints: BTreeMap>>, /// Aliases of merge commits whose second parent still needs a new /// lane. - merge_commits: Vec, + merge_commits: Vec, /// Scratch list of the [`DeltaOp`]s recorded for processing commit pending_delta: Vec, @@ -65,16 +73,16 @@ impl Buffer { /// Remember `alias` as a merge commit whose second parent must be /// given its own lane. - pub fn track_merge_commit(&mut self, alias: AliasId) { + pub fn track_merge_commit(&mut self, alias: CommitAlias) { self.merge_commits.push(alias); } - pub fn update(&mut self, new_chunk: &Chunk) { + pub fn update(&mut self, new_chunk: &LaneSlot) { // Phase 1: place the new chunk into the lane array. let placement_index = self.place_chunk(new_chunk); // Phase 2: consume the alias in all other live chunks. - if let Some(alias) = new_chunk.alias { + if let Some(alias) = new_chunk.alias() { self.consume_alias_in_other_chunks( alias, placement_index, @@ -88,10 +96,11 @@ impl Buffer { self.commit_delta(); } - fn place_chunk(&mut self, new_chunk: &Chunk) -> usize { - // Prefer a lane whose current occupant is waiting for this chunk as parent_a. + fn place_chunk(&mut self, new_chunk: &LaneSlot) -> usize { + // Prefer a lane whose current occupant awaits this chunk's + // commit as its primary parent. let target = self - .find_lane_awaiting_parent(new_chunk.alias) + .find_lane_awaiting_parent(new_chunk.alias()) .or_else(|| self.first_empty_lane()) .unwrap_or(self.current.len()); @@ -105,12 +114,14 @@ impl Buffer { fn find_lane_awaiting_parent( &self, - alias: Option, + alias: Option, ) -> Option { - let alias = alias?; - self.current.iter().position(|slot| { - slot.as_ref() - .is_some_and(|chunk| chunk.parent_a == Some(alias)) + alias.and_then(|alias| { + self.current.iter().position(|slot| { + slot.as_ref().is_some_and(|chunk| { + chunk.awaits() == Some(alias) + }) + }) }) } @@ -120,43 +131,52 @@ impl Buffer { fn consume_alias_in_other_chunks( &mut self, - alias: AliasId, + alias: CommitAlias, skip_index: usize, ) { - for index in 0..self.current.len() { - let mut chunk = match self.current[index].clone() { + let current = self.current.clone(); + for (index, slot) in current.into_iter().enumerate() { + let chunk = match slot { Some(chunk) if index != skip_index => chunk, _ => continue, }; - let changed_a = chunk.parent_a == Some(alias); - let changed_b = chunk.parent_b == Some(alias); - - if changed_a || changed_b { - if changed_a { - chunk.parent_a = None; - } - if changed_b { - chunk.parent_b = None; + let new = match chunk { + // The awaited parent was JUST placed. Close the lane. + // The pending second parent is dropped with it. + LaneSlot::Flowing { parent, .. } + | LaneSlot::FlowingMerge { parent, .. } + | LaneSlot::Reserved { parent } + if parent.get() == alias => + { + None } + // The second parent was just placed + // bridge resolves and the lane flows + LaneSlot::FlowingMerge { + alias: merge_alias, + parent, + second, + } if second.get() == alias => Some(LaneSlot::Flowing { + alias: merge_alias, + parent, + }), + _ => continue, + }; - self.record_replace( - index, - chunk.parent_a.is_some().then_some(chunk), - ); - } + self.record_replace(index, new); } } fn flush_merge_commits(&mut self) { while let Some(alias) = self.merge_commits.pop() { // Search for an occupied slot that matches the target alias. - // If found, extract its index and a mutable clone of the chunk. - let Some((index, mut chunk)) = + // If found, extract its index and a clone of the chunk. + let Some((index, chunk)) = self.current.iter().enumerate().find_map( |(index, slot)| { let chunk = slot.as_ref()?; - (chunk.alias == Some(alias)) + (chunk.alias() == Some(alias)) .then(|| (index, chunk.clone())) }, ) @@ -164,26 +184,33 @@ impl Buffer { continue; }; - let detached_parent = chunk.parent_b.take(); - self.record_replace(index, Some(chunk)); - - if let Some(parent) = detached_parent { - let new_lane = Chunk { - alias: None, - parent_a: Some(parent), - parent_b: None, - marker: Markers::Commit, - }; - - // Always append the merge's second-parent lane to - // the end instead of reusing an existing empty slot, - // so the new visual column does not collapse - // spatial ordering of lanes already in existence. - self.record_insert( - self.current.len(), - Some(new_lane), - ); - } + // Only a merge still owing its second parent needs a + // lane split off; anything else is a no-op. + let LaneSlot::FlowingMerge { + alias: merge_alias, + parent, + second, + } = chunk + else { + continue; + }; + + self.record_replace( + index, + Some(LaneSlot::Flowing { + alias: merge_alias, + parent, + }), + ); + + // Always append the merge's second-parent lane to + // the end instead of reusing an existing empty slot, + // so the new visual column does not collapse + // spatial ordering of lanes already in existence. + self.record_insert( + self.current.len(), + Some(LaneSlot::Reserved { parent: second }), + ); } } @@ -202,7 +229,11 @@ impl Buffer { } } - fn record_replace(&mut self, index: usize, new: Option) { + fn record_replace( + &mut self, + index: usize, + new: Option, + ) { self.pending_delta.push(DeltaOp::Replace { index, new: new.clone(), @@ -210,7 +241,11 @@ impl Buffer { self.current[index] = new; } - fn record_insert(&mut self, index: usize, item: Option) { + fn record_insert( + &mut self, + index: usize, + item: Option, + ) { self.pending_delta.push(DeltaOp::Insert { index, item: item.clone(), @@ -227,7 +262,7 @@ impl Buffer { &self, start: usize, end: usize, - ) -> Vec>> { + ) -> Vec>> { let (current_index, mut state) = self.checkpoints.range(..=start).next_back().map_or_else( || (None, Vec::new()), @@ -261,7 +296,7 @@ impl Buffer { } fn apply_delta_to_state( - state: &mut Vec>, + state: &mut Vec>, delta: &Delta, ) { for op in &delta.0 { From 733648de8b8e386b11c1696c86f2cb140a1769bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 9 Jul 2026 13:27:29 -0400 Subject: [PATCH 57/60] refactor walker for LaneSlot, add processed set --- asyncgit/src/graph/walker.rs | 158 ++++++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 47 deletions(-) diff --git a/asyncgit/src/graph/walker.rs b/asyncgit/src/graph/walker.rs index d64774b1af..e611c2c59e 100644 --- a/asyncgit/src/graph/walker.rs +++ b/asyncgit/src/graph/walker.rs @@ -1,8 +1,9 @@ use super::buffer::Buffer; -use super::chunk::{Chunk, Markers}; +use super::chunk::LaneSlot; use super::oids::GraphOids; use super::{ - AliasId, ConnectionType, GraphRow, LaneIndex, MAX_LANE_COLORS, + CommitAlias, ConnectionType, GraphRow, LaneIndex, UnwalkedAlias, + MAX_LANE_COLORS, }; use crate::sync::CommitId; use core::cmp::Ordering; @@ -145,10 +146,14 @@ fn overlay_cell( pub struct GraphWalker { pub buffer: Buffer, pub oids: GraphOids, - pub branch_lane_map: HashMap, /// Maps a merge commit's alias to the alias of its second parent. - pub merge_parents: HashMap, + pub merge_parents: HashMap, + + /// Aliases of commits already folded into the buffer; consulted + /// by [`Self::drawable_parent`], which refuses to mint an + /// [`UnwalkedAlias`] for any of them. + processed: HashSet, } impl Default for GraphWalker { @@ -162,11 +167,22 @@ impl GraphWalker { Self { buffer: Buffer::new(), oids: GraphOids::new(), - branch_lane_map: HashMap::new(), merge_parents: HashMap::new(), + processed: HashSet::new(), } } + /// Mint the drawable alias for a parent commit, or `None` if the + /// walk already passed it. + fn drawable_parent( + &mut self, + id: &CommitId, + ) -> Option { + let alias = self.oids.get_or_insert(id); + (!self.processed.contains(&alias)) + .then_some(UnwalkedAlias(alias)) + } + pub fn process( &mut self, commit_id: CommitId, @@ -174,31 +190,42 @@ impl GraphWalker { ) { let commit_alias = self.oids.get_or_insert(&commit_id); - let mut mapped_parents = parents + let mut drawable_parents = parents .iter() - .map(|parent_id| self.oids.get_or_insert(parent_id)); + .filter_map(|parent_id| self.drawable_parent(parent_id)); // We explicitly cap support at 2 parents, ignoring octo/mega merges. - let first_parent = mapped_parents.next(); - let second_parent = mapped_parents.next(); - - let chunk = Chunk { - alias: Some(commit_alias), - parent_a: first_parent, - parent_b: second_parent, - marker: Markers::Commit, + let first_parent = drawable_parents.next(); + let second_parent = drawable_parents.next(); + + let chunk = match (first_parent, second_parent) { + (Some(parent), Some(second)) => LaneSlot::FlowingMerge { + alias: commit_alias, + parent, + second, + }, + (Some(parent), None) => LaneSlot::Flowing { + alias: commit_alias, + parent, + }, + // `second_parent` is only ever `Some` once + // `first_parent` has already been consumed from the + // identical iterator, so a merge always has both parents. + (None, _) => LaneSlot::Settled { + alias: commit_alias, + }, }; - if let Some(second) = second_parent { + if let LaneSlot::FlowingMerge { second, .. } = &chunk { + let second = second.get(); self.merge_parents.insert(commit_alias, second); - if first_parent.is_some() - && !self.is_redundant_merge_track(second) - { + if !self.is_redundant_merge_track(second) { self.buffer.track_merge_commit(commit_alias); } } + self.processed.insert(commit_alias); self.buffer.update(&chunk); } @@ -261,8 +288,8 @@ impl GraphWalker { lanes: &mut [Option<(ConnectionType, LaneIndex)>], merge_bridge: Option<(usize, usize)>, commit_lane: usize, - current_snapshot: &[Option], - previous_snapshot: Option<&[Option]>, + current_snapshot: &[Option], + previous_snapshot: Option<&[Option]>, ) { let Some((source_lane, target_lane)) = merge_bridge else { return; @@ -349,22 +376,28 @@ impl GraphWalker { .collect() } - /// Checks if tracking a merge commit would be redundant based on current buffer state. + /// Checks if tracking a merge commit would be redundant based on + /// current buffer state: some lane already flows to the target + /// parent without owing a second parent of its own. fn is_redundant_merge_track( &self, - target_parent: AliasId, + target_parent: CommitAlias, ) -> bool { - self.buffer.current.iter().flatten().any(|commit| { - commit.parent_a == Some(target_parent) - && commit.parent_b.is_none() + self.buffer.current.iter().flatten().any(|slot| { + matches!( + slot, + LaneSlot::Flowing { parent, .. } + | LaneSlot::Reserved { parent } + if parent.get() == target_parent + ) }) } /// Determines if a lane should draw an upward-connecting corner. fn lane_continues_upwards( target_lane: usize, - current_snapshot: &[Option], - previous_snapshot: Option<&[Option]>, + current_snapshot: &[Option], + previous_snapshot: Option<&[Option]>, ) -> bool { let exists_in_current = current_snapshot.get(target_lane).is_some(); @@ -427,8 +460,8 @@ impl GraphWalker { pub fn render_row( &self, commit_id: &CommitId, - current_snapshot: &[Option], - previous_snapshot: Option<&[Option]>, + current_snapshot: &[Option], + previous_snapshot: Option<&[Option]>, branch_tips: &HashSet, stashes: &HashSet, head_id: Option<&CommitId>, @@ -467,7 +500,7 @@ impl GraphWalker { let chunk = chunk_option.as_ref()?; // Returns None early if the chunk is missing if commit_alias.is_some() - && chunk.alias == commit_alias + && chunk.alias() == commit_alias { let connection = Self::determine_commit_connection( @@ -523,8 +556,8 @@ impl GraphWalker { /// Locates the primary lane for the current commit. fn find_commit_lane( - current_snapshot: &[Option], - commit_alias: Option, + current_snapshot: &[Option], + commit_alias: Option, ) -> usize { let Some(target_alias) = commit_alias else { return 0; @@ -534,7 +567,7 @@ impl GraphWalker { .iter() .position(|chunk_option| { chunk_option.as_ref().is_some_and(|chunk| { - chunk.alias == Some(target_alias) + chunk.alias() == Some(target_alias) }) }) .unwrap_or(0) @@ -542,15 +575,15 @@ impl GraphWalker { /// Computes the span (min, max) between the commit's lane and its second parent's lane. fn calculate_merge_bridge( - current_snapshot: &[Option], + current_snapshot: &[Option], commit_lane: usize, - second_parent_alias: AliasId, + second_parent_alias: CommitAlias, ) -> Option<(usize, usize)> { current_snapshot .iter() .position(|chunk_option| { chunk_option.as_ref().is_some_and(|chunk| { - chunk.parent_a == Some(second_parent_alias) + chunk.awaits() == Some(second_parent_alias) }) }) .map(|target_lane| { @@ -563,8 +596,8 @@ impl GraphWalker { /// Identifies lanes that existed in the previous row but terminated before the current row. fn find_branching_lanes( - current_snapshot: &[Option], - previous_snapshot: Option<&[Option]>, + current_snapshot: &[Option], + previous_snapshot: Option<&[Option]>, ) -> Vec { let Some(previous) = previous_snapshot else { return Vec::new(); @@ -599,21 +632,19 @@ impl GraphWalker { /// Determines the correct vertical line style for non-commit passthrough lanes. fn determine_passthrough_connection( - chunk: &Chunk, + chunk: &LaneSlot, lane_index: usize, - head_alias: Option, + head_alias: Option, ) -> Option { - let is_orphan = - chunk.parent_a.is_none() && chunk.parent_b.is_none(); - - if is_orphan { + // A settled lane draws nothing below its commit. + if matches!(chunk, LaneSlot::Settled { .. }) { return None; } let is_dotted = lane_index == 0 && head_alias.is_some() - && (chunk.parent_a == head_alias - || chunk.parent_b == head_alias); + && (chunk.awaits() == head_alias + || chunk.second() == head_alias); if is_dotted { Some(ConnectionType::VerticalDotted) @@ -814,6 +845,39 @@ mod tests { ); } + #[test] + fn skewed_parent_before_child_leaves_no_phantom_lane() { + //1 to 2 must be dropped instead of + // opening a lane that waits for 2 until the end of the walk. + let rows = render(&[(2, &[3]), (1, &[2, 3]), (3, &[])]); + assert_eq!(rows, vec!["o", "┃ o", "o━┛"]); + } + + #[test] + fn complete_walk_settles_all_lanes() { + // Both parents of 1 appear before it in the walk (clock + // skew). After a complete walk no lane may still wait on a + // parent, creating the evil phantom lines + let history: &[(usize, &[usize])] = + &[(2, &[4]), (3, &[4]), (1, &[2, 3]), (4, &[])]; + + assert_eq!(render(history), vec!["o", "┃ o", "┃ ┃ o", "o━┛"]); + + let mut walker = GraphWalker::new(); + for (commit, parents) in history { + let parents: Vec = + parents.iter().map(|p| id(*p)).collect(); + walker.process(id(*commit), &parents); + } + + for slot in walker.buffer.current.iter().flatten() { + assert!( + matches!(slot, LaneSlot::Settled { .. }), + "lane still waiting on a parent after a complete walk: {slot:?}" + ); + } + } + #[test] fn crossed_lane_keeps_own_color() { let rows = &[ From 715a00e28073d6021a95aa4f6d6bfd9a54abda1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 9 Jul 2026 13:27:32 -0400 Subject: [PATCH 58/60] add graphrow symbol tests --- src/components/utils/graphrow.rs | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/components/utils/graphrow.rs b/src/components/utils/graphrow.rs index e45bae89a7..89cb6bb27a 100644 --- a/src/components/utils/graphrow.rs +++ b/src/components/utils/graphrow.rs @@ -16,3 +16,40 @@ pub const SYM_TEE_RIGHT: &str = "┣"; pub const SYM_TEE_UP: &str = "┻"; pub const SYM_TEE_DOWN: &str = "┳"; pub const SYM_SPACE: &str = " "; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn symbols_are_single_char_and_distinct() { + let symbols = [ + SYM_COMMIT, + SYM_COMMIT_BRANCH, + SYM_COMMIT_MERGE, + SYM_COMMIT_STASH, + SYM_COMMIT_UNCOMMITTED, + SYM_VERTICAL, + SYM_VERTICAL_DOTTED, + SYM_HORIZONTAL, + SYM_MERGE_BRIDGE_START, + SYM_MERGE_BRIDGE_MID, + SYM_MERGE_BRIDGE_END, + SYM_BRANCH_UP, + SYM_BRANCH_UP_RIGHT, + SYM_TEE_LEFT, + SYM_TEE_RIGHT, + SYM_TEE_UP, + SYM_TEE_DOWN, + SYM_SPACE, + ]; + + for symbol in symbols { + assert_eq!( + symbol.chars().count(), + 1, + "{symbol:?} should be a single glyph" + ); + } + } +} From 6b1951636e661381e09291f769946cdbde7b83e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 9 Jul 2026 13:37:40 -0400 Subject: [PATCH 59/60] cargo udate --- Cargo.lock | 1472 +++++++++++++++++++++++++--------------------------- 1 file changed, 697 insertions(+), 775 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2eae56e50e..88ccebaa3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -78,9 +78,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -93,43 +93,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d26c0798709bf56d9ed84af3" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "approx" @@ -142,18 +143,18 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.8.0" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ "rustversion", ] [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "asyncgit" @@ -168,6 +169,7 @@ dependencies = [ "git2", "git2-hooks", "gix", + "im", "invalidstring", "log", "openssl-sys", @@ -196,9 +198,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "backtrace" @@ -229,9 +231,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bcrypt-pbkdf" @@ -298,6 +300,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -328,13 +339,13 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.0" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" dependencies = [ "memchr", "regex-automata", - "serde", + "serde_core", ] [[package]] @@ -350,9 +361,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bwrap" @@ -383,15 +394,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytesize" -version = "2.3.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3" +checksum = "3d7c8918969267b2932ffd5655509bbbea0833823058c378876953217f5fc50e" [[package]] name = "castaway" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] @@ -407,10 +418,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.7" +version = "1.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" +checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -441,9 +453,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "num-traits", @@ -462,18 +474,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.57" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -483,21 +495,24 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "clru" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59" +checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" +dependencies = [ + "hashbrown 0.16.1", +] [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "compact_str" @@ -516,9 +531,9 @@ dependencies = [ [[package]] name = "console" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" dependencies = [ "encode_unicode", "libc", @@ -548,9 +563,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -566,18 +581,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -594,9 +609,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crossterm" @@ -608,7 +623,7 @@ dependencies = [ "crossterm_winapi", "mio", "parking_lot", - "rustix 0.38.43", + "rustix 0.38.44", "serde", "signal-hook", "signal-hook-mio", @@ -627,7 +642,7 @@ dependencies = [ "document-features", "mio", "parking_lot", - "rustix 1.1.3", + "rustix 1.1.4", "serde", "signal-hook", "signal-hook-mio", @@ -657,9 +672,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -707,14 +722,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "darling" -version = "0.20.10" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ "darling_core", "darling_macro", @@ -722,34 +737,33 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -759,6 +773,37 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "deltae" version = "0.3.2" @@ -767,9 +812,9 @@ checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "zeroize", @@ -780,9 +825,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] [[package]] name = "derive_more" @@ -803,7 +845,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -847,13 +889,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -873,9 +915,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "easy-cast" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f40539c229fc2e4674bdecdf24bfcc2cb83631ca911c78a035fa9f2381c32b" +checksum = "59e68c9e2b6f969aa527564744c81d1ad24da08a36422a268ec6b61002a7eecd" [[package]] name = "ecdsa" @@ -902,9 +944,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", @@ -914,9 +956,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elliptic-curve" @@ -954,25 +996,19 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", "regex", ] -[[package]] -name = "env_home" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" - [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -983,18 +1019,18 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1045,15 +1081,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "ff" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ "rand_core", "subtle", @@ -1078,14 +1114,12 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.25" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox", - "windows-sys 0.59.0", ] [[package]] @@ -1096,6 +1130,12 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "finl_unicode" version = "1.4.0" @@ -1110,9 +1150,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -1126,9 +1166,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foldhash" @@ -1138,9 +1178,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -1156,15 +1196,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1173,20 +1213,19 @@ dependencies = [ [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-task", "pin-project-lite", - "pin-utils", "slab", ] @@ -1212,9 +1251,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -1235,15 +1274,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "wasip2", - "wasip3", ] [[package]] @@ -1289,7 +1326,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1378,7 +1415,7 @@ dependencies = [ "two-face", "unicode-segmentation", "unicode-truncate", - "unicode-width 0.2.0", + "unicode-width 0.2.2", "which", ] @@ -1449,9 +1486,9 @@ dependencies = [ [[package]] name = "gix-attributes" -version = "0.30.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f868f013fee0ebb5c85fae848c34a0b9ef7438acfbaec0c82a3cdbd5eac730a0" +checksum = "9e72da5a1c35c9a129be0c60ab9968779981ca50835dd98650ecd8b0ea4d721e" dependencies = [ "bstr", "gix-glob", @@ -1466,9 +1503,9 @@ dependencies = [ [[package]] name = "gix-bitmap" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e150161b8a75b5860521cb876b506879a3376d3adc857ec7a9d35e7c6a5e531" +checksum = "d982fc7ef0608e669851d0d2a6141dae74c60d5a27e8daa451f2a4857bbf41e2" dependencies = [ "thiserror 2.0.18", ] @@ -1484,9 +1521,9 @@ dependencies = [ [[package]] name = "gix-command" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745bc165b7da500acc26d24888379ae0dfd1ecabe3a47420cdcb92feefb0561d" +checksum = "2962172c6f78731e2b7773bf762f7b8d1746a342a4c0a8914a612206e1295953" dependencies = [ "bstr", "gix-path", @@ -1530,9 +1567,9 @@ dependencies = [ [[package]] name = "gix-config-value" -version = "0.17.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563361198101cedc975fe5760c91ac2e4126eec22216e81b659b45289feaf1ea" +checksum = "4378c53ec3db049919edf91ff76f56f28886a8b4b4a5a9dc633108d84afc3675" dependencies = [ "bitflags 2.13.0", "bstr", @@ -1624,9 +1661,9 @@ dependencies = [ [[package]] name = "gix-features" -version = "0.46.0" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a407957e21dc5e6c87086e50e5114a2f9240f9cb11699588a6d900d53cb6c70" +checksum = "752493cd4b1d5eaaa0138a7493f65c96863fefa990fc021e0e519579e389ab20" dependencies = [ "crc32fast", "crossbeam-channel", @@ -1665,9 +1702,9 @@ dependencies = [ [[package]] name = "gix-fs" -version = "0.19.0" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba74fa163d3b2ba821d5cd207d55fe3daac3d1099613a8559c812d2b15b3c39a" +checksum = "a964b4aec683eb0bacb87533defa80805bb4768056371a47ab38b00a2d377b72" dependencies = [ "bstr", "fastrand", @@ -1691,9 +1728,9 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b8e11ea6bbd0fd4ab4a1c66812dd3cc25921a41315b120f352997725a4c79d6" +checksum = "d8ced05d2d7b13bff08b2f7eb4e47cfeaf00b974c2ddce08377c4fe1f706b3eb" dependencies = [ "faster-hex", "gix-features", @@ -1714,9 +1751,9 @@ dependencies = [ [[package]] name = "gix-ignore" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8953d87c13267e296d547f0fc7eaa8aa8fa5b2a9a34ab1cd5857f25240c7d299" +checksum = "09f915dcf6911e3027537166d34e13f0fe101ed12225178d2ae29cd1272cff26" dependencies = [ "bstr", "gix-glob", @@ -1748,16 +1785,16 @@ dependencies = [ "itoa", "libc", "memmap2", - "rustix 1.1.3", + "rustix 1.1.4", "smallvec", "thiserror 2.0.18", ] [[package]] name = "gix-lock" -version = "21.0.0" +version = "21.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d406220ef9df105645a9ddcaa42e8c882ba920344ace866d0403570aea599" +checksum = "054fbd0989700c69dc5aa80bc66944f05df1e15aa7391a9e42aca7366337905f" dependencies = [ "gix-tempfile", "gix-utils", @@ -1839,9 +1876,9 @@ dependencies = [ [[package]] name = "gix-packetline" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c333badf342e9c2392800a96b9f2cf5bcb33906d2577d6ec923756ff4008a3f" +checksum = "b217dd0ee0c4021ecf169a4a519b1b4f80d15e3f3765f3dc466223dc0ac891d7" dependencies = [ "bstr", "faster-hex", @@ -1851,9 +1888,9 @@ dependencies = [ [[package]] name = "gix-path" -version = "0.11.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c3cd795cad18c7acbc6bafe34bfb34ac7273ee81133793f9d1516dd9faf922" +checksum = "c8fd1fe596dc393b538e1d5492c5585971a9311475b3255f7b889023df208476" dependencies = [ "bstr", "gix-trace", @@ -1863,9 +1900,9 @@ dependencies = [ [[package]] name = "gix-pathspec" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6fd8e514d8b99ec5042ee17909a17750ccf54d0b8b30c850954209c800322" +checksum = "c7f4cc23f55ca7c190bf243f1a4e2139d4522022f724fb0dfc06c93f65a01ef6" dependencies = [ "bitflags 2.13.0", "bstr", @@ -1897,9 +1934,9 @@ dependencies = [ [[package]] name = "gix-quote" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e912ec04b7b1566a85ad486db0cab6b9955e3e32bcd3c3a734542ab3af084c5b" +checksum = "96fc2ff2ec8cc0c92807f02eab1f00eb02619fc2810d13dc42679492fcc36757" dependencies = [ "bstr", "gix-utils", @@ -1979,9 +2016,9 @@ dependencies = [ [[package]] name = "gix-sec" -version = "0.13.0" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beeb3bc63696cf7acb5747a361693ebdbcaf25b5d27d2308f38e9782983e7bce" +checksum = "283f4a746c9bde8550be63e6f961ff4651f412ca12666e8f5615f39464960ab9" dependencies = [ "bitflags 2.13.0", "gix-path", @@ -1991,9 +2028,9 @@ dependencies = [ [[package]] name = "gix-shallow" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f4660fed3786d28e7e57d31b2de9ab3bf846068e187ccc52ee513de19a0073" +checksum = "189386b5da5285216cc0ede89eff5a943d5261fc794241ee6ec5360b77df15ad" dependencies = [ "bstr", "gix-hash", @@ -2041,9 +2078,9 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "21.0.0" +version = "21.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d280bba7c547170e42d5228fc6e76c191fb5a7c88808ff61af06460404d1fd91" +checksum = "d22227f6b203f511ff451c33c89899e87e4f571fc596b06f68e6e613a6508528" dependencies = [ "dashmap", "gix-fs", @@ -2054,9 +2091,9 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.17" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e42a4c2583357721ba2d887916e78df504980f22f1182df06997ce197b89504" +checksum = "44dc45eae785c0eb14173e0f152e6e224dcf4d45b6a6999a3aed22af541ad678" [[package]] name = "gix-transport" @@ -2093,9 +2130,9 @@ dependencies = [ [[package]] name = "gix-url" -version = "0.35.0" +version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca2e50308a8373069e71970939f43ea4a1b5f422cf807d048ebcf07dcc02b2c" +checksum = "1a61ead12e33fa52ae92b207ee27554f646a8e7a3dad8b78da1582ec91eda0a6" dependencies = [ "bstr", "gix-path", @@ -2105,9 +2142,9 @@ dependencies = [ [[package]] name = "gix-utils" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "befcdbdfb1238d2854591f760a48711bed85e72d80a10e8f2f93f656746ef7c5" +checksum = "66c50966184123caf580ffa64e28031a878597f1c7fceb8fe19566c38eb1b771" dependencies = [ "bstr", "fastrand", @@ -2116,9 +2153,9 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec1eff98d91941f47766367cba1be746bab662bad761d9891ae6f7882f7840b" +checksum = "7bc6fc771c4063ba7cd2f47b91fb6076251c6a823b64b7fe7b8874b0fe4afae3" dependencies = [ "bstr", ] @@ -2169,11 +2206,11 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash 0.1.4", + "foldhash 0.1.5", ] [[package]] @@ -2231,16 +2268,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.62.2", ] [[package]] @@ -2254,21 +2292,23 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", + "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -2277,105 +2317,61 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ - "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", + "icu_locale_core", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -2384,9 +2380,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -2395,46 +2391,61 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", ] +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + [[package]] name = "imara-diff" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.5", ] [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", + "hashbrown 0.17.1", ] [[package]] name = "indoc" -version = "2.0.5" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] [[package]] name = "inotify" -version = "0.11.0" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" dependencies = [ "bitflags 2.13.0", "inotify-sys", @@ -2443,18 +2454,18 @@ dependencies = [ [[package]] name = "inotify-sys" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" dependencies = [ "libc", ] [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "block-padding", "generic-array", @@ -2476,15 +2487,15 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.6" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "894813a444908c0c8c0e221b041771d107c4a21de1d317dc49bcc66e3c9e5b3f" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ "darling", "indoc", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2493,9 +2504,9 @@ version = "0.1.3" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -2508,41 +2519,42 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "961d16382652bfdd8c6f68b223b26a8c93e0d475c672f414411db31c6c5c900e" dependencies = [ + "defmt", "jiff-static", "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-link", ] [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "d0879bd39df99c4c5e2c6615ccc026391a423dde10532c573e6086eb94a802cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "jiff-tzdb" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" [[package]] name = "jiff-tzdb-platform" @@ -2555,38 +2567,41 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] [[package]] name = "kasuari" -version = "0.4.8" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481b4381c813cebeca86bd55c781d21f902f34cf927ec08d6df3dfebcfd2002" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" dependencies = [ "hashbrown 0.16.1", + "portable-atomic", "thiserror 2.0.18", ] [[package]] name = "kqueue" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" dependencies = [ "kqueue-sys", "libc", @@ -2594,11 +2609,11 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.4" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.13.0", "libc", ] @@ -2626,17 +2641,11 @@ dependencies = [ "spin", ] -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" -version = "0.2.180" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libgit2-sys" @@ -2653,26 +2662,24 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.11" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ - "bitflags 2.13.0", "libc", - "redox_syscall", ] [[package]] name = "libz-sys" -version = "1.1.21" +version = "1.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" dependencies = [ "cc", "libc", @@ -2697,15 +2704,15 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" @@ -2724,15 +2731,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" +checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" dependencies = [ "hashbrown 0.17.1", ] @@ -2749,26 +2756,26 @@ dependencies = [ [[package]] name = "maybe-async" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmap2" -version = "0.9.9" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -2796,23 +2803,24 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.7" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2870,26 +2878,28 @@ dependencies = [ [[package]] name = "notify-types" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.13.0", +] [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", @@ -2914,7 +2924,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2928,11 +2938,10 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -2973,15 +2982,21 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "onig" -version = "6.5.1" +version = "6.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" +checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" dependencies = [ "bitflags 2.13.0", "libc", @@ -2991,9 +3006,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.9.1" +version = "69.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" +checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7" dependencies = [ "cc", "pkg-config", @@ -3013,18 +3028,18 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.4.1+3.4.0" +version = "300.6.1+3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" +checksum = "46eb8fb9fb3b61ce1c0f8a026c4c1a0714d3a9e138e7fbde78753ce2babc3846" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -3062,9 +3077,9 @@ dependencies = [ [[package]] name = "p384" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" dependencies = [ "ecdsa", "elliptic-curve", @@ -3107,7 +3122,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3153,15 +3168,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.6" +version = "2.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +checksum = "47627dd7305c6a2d6c8c6bcd24c5a4c17dbbf425f4f9c5313e724b38fc9782e9" dependencies = [ "memchr", "ucd-trie", @@ -3169,9 +3184,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.6" +version = "2.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +checksum = "4b4254325ecad416ab689e27ba51da03ba01a9632bc6e108f5fe7c3c4ad29d58" dependencies = [ "pest", "pest_generator", @@ -3179,25 +3194,24 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.6" +version = "2.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +checksum = "6c4c0e91ead7a8f7acecbca6f003fc2e8282b1dbe2dd9c9d2f16aba42995e0a7" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "pest_meta" -version = "2.8.6" +version = "2.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +checksum = "f9744bc48116fee06334924bb5f2bad41eed5e89bd26e29b0b799f9a3f82c210" dependencies = [ "pest", - "sha2", ] [[package]] @@ -3240,7 +3254,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3254,15 +3268,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkcs1" @@ -3287,9 +3295,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plist" @@ -3329,19 +3337,28 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.10.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3350,9 +3367,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] @@ -3367,16 +3384,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", -] - [[package]] name = "primeorder" version = "0.13.6" @@ -3388,9 +3395,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -3415,9 +3422,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.38" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -3436,9 +3443,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "rand_chacha", "rand_core", @@ -3460,18 +3467,28 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.17", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core", ] [[package]] name = "ratatui" -version = "0.30.1" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1695748e3a735b34968c887ceea5a380b43545903868ae8f5b666593100f6b68" +checksum = "3274ba0a2c5e1bcad2a2005d20f4dc59dad26b2eb0940fb094500dba4099d57d" dependencies = [ "instability", "ratatui-core", "ratatui-crossterm", + "ratatui-termina", "ratatui-termion", "ratatui-termwiz", "ratatui-widgets", @@ -3480,14 +3497,13 @@ dependencies = [ [[package]] name = "ratatui-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3603f354bba8c595fa47860e60142d7372b7210c27044c6a7d0e1a4336b44" +checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" dependencies = [ "bitflags 2.13.0", "compact_str", "hashbrown 0.17.1", - "indoc", "itertools", "kasuari", "lru", @@ -3497,14 +3513,14 @@ dependencies = [ "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", - "unicode-width 0.2.0", + "unicode-width 0.2.2", ] [[package]] name = "ratatui-crossterm" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2867bedcbd6a690ca4f8672a687b730ec07660c79844517b084311b529980c" +checksum = "567584a3b0e6a8203c23de40b4861497266725eb5363dbfd18a1edd603cca9f0" dependencies = [ "cfg-if", "crossterm 0.28.1", @@ -3513,11 +3529,22 @@ dependencies = [ "ratatui-core", ] +[[package]] +name = "ratatui-termina" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0bf912d9e66f057a759d92e386a280ea886b352ab757d6ac4d653c7ed2c43c2" +dependencies = [ + "instability", + "ratatui-core", + "termina", +] + [[package]] name = "ratatui-termion" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c16cc35a9d9114e0b2bb4b22018b96ae7f5fe60e2595dc73e622b4e78624835" +checksum = "87c732202fa5a71a9da0991013f0853e53f87048f45198e3a1ca3ee722accc2f" dependencies = [ "instability", "ratatui-core", @@ -3526,9 +3553,9 @@ dependencies = [ [[package]] name = "ratatui-termwiz" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "386b8ff8f74ed749509391c56d549761a2fcdb408e1f42e467286bcb7dac8967" +checksum = "faf03e0380b7744054d6cb74224fe3adf062a029754933f575ca1e3b4c2ce977" dependencies = [ "ratatui-core", "termwiz", @@ -3543,14 +3570,14 @@ dependencies = [ "ratatui-core", "ratatui-crossterm", "ratatui-widgets", - "unicode-width 0.2.0", + "unicode-width 0.2.2", ] [[package]] name = "ratatui-widgets" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef4f17dd7ac3abf5adc2b920a03c61eee4bfe6a88fa5191936895525371d79c" +checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" dependencies = [ "bitflags 2.13.0", "hashbrown 0.17.1", @@ -3563,14 +3590,14 @@ dependencies = [ "strum", "time", "unicode-segmentation", - "unicode-width 0.2.0", + "unicode-width 0.2.2", ] [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -3588,29 +3615,29 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.13.0", ] [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.17", "libredox", "thiserror 2.0.18", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" dependencies = [ "aho-corasick", "memchr", @@ -3620,9 +3647,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" dependencies = [ "aho-corasick", "memchr", @@ -3631,9 +3658,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rfc6979" @@ -3647,9 +3674,9 @@ dependencies = [ [[package]] name = "ron" -version = "0.12.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" +checksum = "81116b9531d61eabc41aeb228e4b6b2435bcca3233b98cf3b3077d4e6e9debb3" dependencies = [ "bitflags 2.13.0", "once_cell", @@ -3661,9 +3688,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.7" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest", @@ -3682,9 +3709,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" [[package]] name = "rustc_version" @@ -3697,9 +3724,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.43" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.13.0", "errno", @@ -3710,28 +3737,28 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags 2.13.0", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -3742,15 +3769,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scc" -version = "2.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4" -dependencies = [ - "sdd", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -3764,12 +3782,6 @@ dependencies = [ "log", ] -[[package]] -name = "sdd" -version = "3.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584e070911c7017da6cb2eb0788d09f43d789029b5877d3e5ecc8acf86ceee21" - [[package]] name = "sec1" version = "0.7.3" @@ -3786,9 +3798,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.24" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -3817,45 +3829,45 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "serde_json" -version = "1.0.135" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] name = "serial_test" -version = "3.3.1" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0b343e184fc3b7bb44dff0705fffcf4b3756ba6aff420dddd8b24ca145e555" +checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d" dependencies = [ "futures-executor", "futures-util", "log", "once_cell", "parking_lot", - "scc", "serial_test_derive", ] [[package]] name = "serial_test_derive" -version = "3.3.1" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f50427f258fb77356e4cd4aa0e87e2bd2c66dbcee41dc405282cae2bfc26c83" +checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3881,9 +3893,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -3898,30 +3910,30 @@ checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" [[package]] name = "shell-words" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] name = "shellexpand" -version = "3.1.1" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" +checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" dependencies = [ "dirs", ] [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -3929,9 +3941,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", @@ -3940,10 +3952,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -3957,6 +3970,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + [[package]] name = "similar" version = "2.7.0" @@ -3975,24 +3994,31 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] -name = "slab" -version = "0.4.9" +name = "sized-chunks" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" dependencies = [ - "autocfg", + "bitmaps", + "typenum", ] +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "spin" @@ -4063,9 +4089,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -4090,22 +4116,22 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "struct-patch" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e986d2cf6e819bd843319120453d837dfdfa31497c3fee4cefa614b2d182d8c" +checksum = "16d4caaaccd69c9b56c5f5b33d4dca462464d3275230e4d2d3739ba6d4bf5bcb" dependencies = [ "struct-patch-derive", ] [[package]] name = "struct-patch-derive" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68c6387c1c7b53060605101b63d93edca618c6cf7ce61839f2ec2a527419fdb5" +checksum = "1671c6f0992b1b4cb4f5f8ea4a58f9a5f7f895a7638ef9690633dcec0aa67944" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4126,7 +4152,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4148,9 +4174,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -4159,13 +4185,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4205,14 +4231,27 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.24.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "termina" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9048a889effe34a5cddee0af7f53285198b16dca3be510858d38dfdb3e62a04e" +dependencies = [ + "bitflags 2.13.0", + "parking_lot", + "rustix 1.1.4", + "signal-hook", "windows-sys 0.61.2", ] @@ -4317,7 +4356,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4328,27 +4367,25 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] name = "time" -version = "0.3.47" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", - "itoa", "libc", "num-conv", "num_threads", @@ -4360,15 +4397,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -4376,9 +4413,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -4386,9 +4423,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -4401,9 +4438,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "two-face" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d112cfd41c1387546416bcf49c4ae2a1fcacda0d42c9e97120e9798c90c0923" +checksum = "39e51b6e60e545cfdae5a4639ff423818f52372211a8d9a3e892b4b0761f76b2" dependencies = [ "serde", "serde_derive", @@ -4418,9 +4455,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.17.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -4445,24 +4482,24 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-truncate" @@ -4472,7 +4509,7 @@ checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ "itertools", "unicode-segmentation", - "unicode-width 0.2.0", + "unicode-width 0.2.2", ] [[package]] @@ -4483,15 +4520,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" - -[[package]] -name = "unicode-xid" -version = "0.2.6" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "universal-hash" @@ -4505,21 +4536,16 @@ dependencies = [ [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -4534,12 +4560,12 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.3" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ "atomic", - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "serde_core", "wasm-bindgen", @@ -4587,58 +4613,37 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen 0.46.0", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4646,55 +4651,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", - "wasm-bindgen-backend", + "syn 2.0.118", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ - "bitflags 2.13.0", - "hashbrown 0.15.2", - "indexmap", - "semver", + "unicode-ident", ] [[package]] @@ -4773,13 +4747,11 @@ dependencies = [ [[package]] name = "which" -version = "8.0.0" +version = "8.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" +checksum = "48d7cd18d4acb58fb3cdfe9ea54e6cd96a4e7d4cc45c56338b236e82dad47248" dependencies = [ - "env_home", - "rustix 1.1.3", - "winsafe", + "libc", ] [[package]] @@ -4800,11 +4772,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4825,23 +4797,27 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.52.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.57.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-targets 0.52.6", + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", + "windows-result 0.4.1", + "windows-strings", ] [[package]] @@ -4852,7 +4828,18 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] @@ -4863,7 +4850,18 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] @@ -4882,12 +4880,21 @@ dependencies = [ ] [[package]] -name = "windows-sys" -version = "0.52.0" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-targets 0.52.6", + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", ] [[package]] @@ -5048,124 +5055,24 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] -[[package]] -name = "winsafe" -version = "0.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - [[package]] name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.13.0", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yansi" @@ -5175,11 +5082,10 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -5187,69 +5093,79 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -5258,17 +5174,23 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "zlib-rs" -version = "0.5.5" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" + +[[package]] +name = "zmij" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" From c943a5d63f3b29af2bbb105dcb4a8dda6f40400f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 9 Jul 2026 13:44:53 -0400 Subject: [PATCH 60/60] parents on demand --- asyncgit/src/revlog.rs | 55 +++++++++++++++++++++++++--------- asyncgit/src/sync/logwalker.rs | 48 +++++++---------------------- asyncgit/src/sync/mod.rs | 4 +-- 3 files changed, 54 insertions(+), 53 deletions(-) diff --git a/asyncgit/src/revlog.rs b/asyncgit/src/revlog.rs index a70c39fd45..ce3a851f71 100644 --- a/asyncgit/src/revlog.rs +++ b/asyncgit/src/revlog.rs @@ -3,7 +3,7 @@ use crate::{ graph::{GraphRow, GraphWalker}, sync::{ gix_repo, repo, CommitId, LogWalker, LogWalkerWithoutFilter, - RepoPath, SharedCommitFilterFn, WalkEntry, + RepoPath, SharedCommitFilterFn, }, AsyncGitNotification, Error, }; @@ -47,9 +47,10 @@ pub struct AsyncLog { filter: Option, partial_extract: AtomicBool, repo: RepoPath, - /// All walk entries collected by the background thread, in walk order. - /// The graph walker reads these lazily, only as far as the viewport requires. - walk_entries: Arc>>, + /// All commit ids collected by the background thread, in walk order. + /// The graph walker reads these lazily, only as far as the viewport + /// requires, looking up each commit's parents on demand. + walk_entries: Arc>>, graph_walker: Arc>, } @@ -83,8 +84,8 @@ impl AsyncLog { /// Computes graph rows for `commit_slice` starting at `global_start`. /// - /// Driven lazily. Processes only as many - /// [`WalkEntry`]s as the viewport requires. + /// Driven lazily. Processes only as many walked commits as the + /// viewport requires, resolving their parents on demand. /// /// Returns `None` when the background walk hasn't reached /// `global_start + commit_slice.len()` yet. @@ -111,8 +112,18 @@ impl AsyncLog { // we know it is yet to have seen let processed = walker.processed_commits().min(needed_end); - for entry in &entries[processed..needed_end] { - walker.process(entry.id, &entry.parents); + + // The graph only needs topology for the commits it is + // about to fold in, so parents are looked up here on + // demand instead of being carried along the whole walk. + if processed < needed_end { + let mut repo = gix_repo(&self.repo).ok()?; + repo.object_cache_size_if_unset(2_usize.pow(14)); + + for id in &entries[processed..needed_end] { + let parents = Self::parents_of(&repo, *id).ok()?; + walker.process(*id, &parents); + } } } @@ -125,6 +136,22 @@ impl AsyncLog { )) } + /// Looks up a commit's (up to two) parents on demand. + /// + /// The graph caps support at two parents, ignoring octopus + /// merges, so anything beyond the first two is dropped here. + fn parents_of( + repo: &gix::Repository, + id: CommitId, + ) -> Result> { + Ok(repo + .find_commit(id)? + .parent_ids() + .take(2) + .map(Into::into) + .collect()) + } + /// pub fn count(&self) -> Result { Ok(self.current.lock()?.commits.len()) @@ -252,7 +279,7 @@ impl AsyncLog { arc_current: &Arc>, arc_background: &Arc, sender: &Sender, - arc_walk_entries: &Arc>>, + arc_walk_entries: &Arc>>, filter: Option, ) -> Result<()> { filter.map_or_else( @@ -309,7 +336,7 @@ impl AsyncLog { arc_current: &Arc>, arc_background: &Arc, sender: &Sender, - arc_walk_entries: &Arc>>, + arc_walk_entries: &Arc>>, ) -> Result<()> { let mut repo: gix::Repository = gix_repo(repo_path)?; let mut walker = @@ -332,15 +359,15 @@ impl AsyncLog { /// to `arc_current` and (when given) moving the full entries into /// `walk_entries` for the graph. fn walk_loop( - mut read: impl FnMut(&mut Vec) -> Result, + mut read: impl FnMut(&mut Vec) -> Result, arc_current: &Arc>, arc_background: &Arc, sender: &Sender, - walk_entries: Option<&Mutex>>, + walk_entries: Option<&Mutex>>, ) -> Result<()> { let start_time = Instant::now(); - let mut entries: Vec = + let mut entries: Vec = Vec::with_capacity(LIMIT_COUNT); loop { @@ -348,7 +375,7 @@ impl AsyncLog { { let mut current = arc_current.lock()?; - current.commits.extend(entries.iter().map(|e| e.id)); + current.commits.extend(entries.iter().copied()); current.duration = start_time.elapsed(); } diff --git a/asyncgit/src/sync/logwalker.rs b/asyncgit/src/sync/logwalker.rs index 29843271f6..c450022948 100644 --- a/asyncgit/src/sync/logwalker.rs +++ b/asyncgit/src/sync/logwalker.rs @@ -2,25 +2,11 @@ use super::{CommitId, SharedCommitFilterFn}; use crate::error::Result; use git2::{Commit, Oid, Repository}; use gix::revision::Walk; -use smallvec::SmallVec; use std::{ cmp::Ordering, collections::{BinaryHeap, HashSet}, }; -/// A commit id together with the ids of its TWO parents. -/// -/// The parents come for free during a walk. -/// Collecting here avoid a second, convulted pass. -#[derive(Debug, Clone)] -pub struct WalkEntry { - /// The commit's own unique identifier. - pub id: CommitId, - - /// The commit's parent identifiers. - pub parents: SmallVec<[CommitId; 2]>, -} - struct TimeOrderedCommit<'a>(Commit<'a>); impl Eq for TimeOrderedCommit<'_> {} @@ -86,16 +72,12 @@ impl<'a> LogWalker<'a> { /// pub fn read( &mut self, - out: &mut Vec, + out: &mut Vec, ) -> Result { let mut count = 0_usize; while let Some(c) = self.commits.pop() { - let mut parents = SmallVec::new(); for p in c.0.parents() { - if parents.len() < 2 { - parents.push(p.id().into()); - } self.visit(p); } @@ -108,7 +90,7 @@ impl<'a> LogWalker<'a> { }; if commit_should_be_included { - out.push(WalkEntry { id, parents }); + out.push(id); } count += 1; @@ -180,20 +162,12 @@ impl<'a> LogWalkerWithoutFilter<'a> { /// pub fn read( &mut self, - out: &mut Vec, + out: &mut Vec, ) -> Result { let mut count = 0_usize; while let Some(Ok(info)) = self.walk.next() { - out.push(WalkEntry { - id: info.id.into(), - parents: info - .parent_ids - .iter() - .take(2) - .map(|id| CommitId::from(*id)) - .collect(), - }); + out.push(info.id.into()); count += 1; @@ -246,7 +220,7 @@ mod tests { walk.read(&mut items).unwrap(); assert_eq!(items.len(), 1); - assert_eq!(items[0].id, oid2); + assert_eq!(items[0], oid2); Ok(()) } @@ -270,12 +244,12 @@ mod tests { let mut walk = LogWalker::new(&repo, 100)?; walk.read(&mut items).unwrap(); - let ids: Vec = items.iter().map(|e| e.id).collect(); + let ids: Vec = items.clone(); let info = get_commits_info(repo_path, &ids, 50).unwrap(); dbg!(&info); assert_eq!(items.len(), 2); - assert_eq!(items[0].id, oid2); + assert_eq!(items[0], oid2); let mut items = Vec::new(); walk.read(&mut items).unwrap(); @@ -305,12 +279,12 @@ mod tests { let mut items = Vec::new(); assert!(matches!(walk.read(&mut items), Ok(2))); - let ids: Vec = items.iter().map(|e| e.id).collect(); + let ids: Vec = items.clone(); let info = get_commits_info(repo_path, &ids, 50).unwrap(); dbg!(&info); assert_eq!(items.len(), 2); - assert_eq!(items[0].id, oid2); + assert_eq!(items[0], oid2); let mut items = Vec::new(); assert!(matches!(walk.read(&mut items), Ok(0))); @@ -352,7 +326,7 @@ mod tests { walker.read(&mut items).unwrap(); assert_eq!(items.len(), 1); - assert_eq!(items[0].id, second_commit_id); + assert_eq!(items[0], second_commit_id); let mut items = Vec::new(); walker.read(&mut items).unwrap(); @@ -399,7 +373,7 @@ mod tests { walker.read(&mut items).unwrap(); assert_eq!(items.len(), 1); - assert_eq!(items[0].id, second_commit_id); + assert_eq!(items[0], second_commit_id); let log_filter = filter_commit_by_search( LogFilterSearch::new(LogFilterSearchOptions { diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 2e76a2128e..1b0c60f8f3 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -72,7 +72,7 @@ pub use hooks::{ }; pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; -pub use logwalker::{LogWalker, LogWalkerWithoutFilter, WalkEntry}; +pub use logwalker::{LogWalker, LogWalkerWithoutFilter}; pub use merge::{ abort_pending_rebase, abort_pending_state, continue_pending_rebase, merge_branch, merge_commit, merge_msg, @@ -267,7 +267,7 @@ pub mod tests { .read(&mut entries) .unwrap(); - entries.iter().map(|e| e.id).collect() + entries } /// Same as `repo_init`, but the repo is a bare repo (--bare)