Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e91b688
add: graph glyphs
philocalyst Mar 24, 2026
72105f0
fix: moving to graph glyphs that fit GITUIs style more
philocalyst Mar 24, 2026
6956349
add: buffer datastructure to hold graph representation
philocalyst Mar 24, 2026
cb5229f
add: buffer methods to facilitate updates against the graph
philocalyst Mar 24, 2026
bf2a36f
add: chunk datastructure
philocalyst Mar 24, 2026
5458042
Connecting all of the modules together
philocalyst Mar 24, 2026
7547363
add: connection type
philocalyst Mar 24, 2026
a663bc8
add: graphrow datatype
philocalyst Mar 24, 2026
f3c01bb
add: OIDS wrapper type
philocalyst Mar 24, 2026
6cf99d8
add: core walker datastructure to stream process
philocalyst Mar 24, 2026
577d1ad
add: walker methods
philocalyst Mar 24, 2026
58d9fd0
refactor: extending the datatypes to work with the graph model
philocalyst Mar 24, 2026
a837bf9
incorporating the graph model into revlog and lib
philocalyst Mar 24, 2026
a7685a4
addition: key toggle for the graph view
philocalyst Mar 24, 2026
a4ccd6a
additions to revlog to support the graph view
philocalyst Mar 24, 2026
b802e10
addition: toggle graph to keybinds
philocalyst Mar 24, 2026
4798d51
incoporated graph view with the commitlist
philocalyst Mar 24, 2026
57bff13
Incoportaed the graph view
philocalyst Mar 24, 2026
5619286
fixed refactor-related errs
philocalyst Mar 24, 2026
a45eef6
Now reusing lanes to remove whitespace
philocalyst Mar 24, 2026
42a7bba
Formatting
philocalyst Mar 24, 2026
a4283bc
unit test #1
philocalyst Mar 24, 2026
40a5cfc
fmt
philocalyst Mar 24, 2026
fb09f56
Added changelog entry
philocalyst Mar 24, 2026
d4c858b
Fixed clippy lints
philocalyst Mar 24, 2026
e74d481
More clippy lints
philocalyst Mar 24, 2026
0b2bb3e
Fixed more clippy lints
philocalyst Mar 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added
* Now with a graph view!
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please match the ususal format


## [0.28.0] - 2025-12-14

**discard changes on checkout**
Expand Down
44 changes: 44 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions asyncgit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ gix = { version = "0.78.0", default-features = false, features = [
"mailmap",
"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"]}
Expand All @@ -39,6 +40,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"
Expand Down
235 changes: 235 additions & 0 deletions asyncgit/src/graph/buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
use super::chunk::{Chunk, Markers};
use im::Vector;
use std::collections::BTreeMap;

#[derive(Clone, Debug)]
pub enum DeltaOp {
Insert { index: usize, item: Option<Chunk> },
Remove { index: usize },
Replace { index: usize, new: Option<Chunk> },
}

#[derive(Clone, Debug)]
pub struct Delta(pub Vec<DeltaOp>);

const CHECKPOINT_INTERVAL: usize = 100;

pub struct Buffer {
pub current: Vector<Option<Chunk>>,
pub deltas: Vec<Delta>,
pub checkpoints: BTreeMap<usize, Vector<Option<Chunk>>>,
mergers: Vec<u32>,
pending_delta: Vec<DeltaOp>,
}

impl Default for Buffer {
fn default() -> Self {
Self::new()
}
}

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 empty_lanes: Vec<usize> = 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 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()));
} 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(),
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 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));
}
}
}
}

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),
);
}
}
}
}

while self.current.last().is_some_and(Option::is_none) {
self.record_remove(self.current.len() - 1);
}

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<Chunk>) {
self.pending_delta.push(DeltaOp::Replace {
index,
new: new.clone(),
});
self.current.set(index, new);
}

fn record_insert(&mut self, index: usize, item: Option<Chunk>) {
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<Vector<Option<Chunk>>> {
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);

if let Some(index) = current_index {
if index >= start && index <= end {
history.push(state.clone());
}
}

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);

if delta_index >= start {
history.push(state.clone());
}
} else {
break;
}
}

history
}

fn apply_delta_to_state(
state: &mut Vector<Option<Chunk>>,
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());
}
}
}
}
}
13 changes: 13 additions & 0 deletions asyncgit/src/graph/chunk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Markers {
Uncommitted,
Commit,
}

#[derive(Clone, Debug)]
pub struct Chunk {
pub alias: Option<u32>,
pub parent_a: Option<u32>,
pub parent_b: Option<u32>,
pub marker: Markers,
}
Loading