From 7cb9132a0edfceab9c261a16803cd3270fa3ee16 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 21 Aug 2026 08:52:44 +0900 Subject: [PATCH 1/5] Move integration tests into crate --- src/lib.rs | 8 ++++++++ {tests => src/tests}/graph.rs | 6 +++--- {tests => src/tests}/mailmap.rs | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) rename {tests => src/tests}/graph.rs (99%) rename {tests => src/tests}/mailmap.rs (99%) diff --git a/src/lib.rs b/src/lib.rs index 57f762c..473c108 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,14 @@ mod keybind; mod view; mod widget; +#[cfg(test)] +#[path = "tests/graph.rs"] +mod graph_tests; + +#[cfg(test)] +#[path = "tests/mailmap.rs"] +mod mailmap_tests; + use std::{path::Path, rc::Rc}; use app::{App, Ret}; diff --git a/tests/graph.rs b/src/tests/graph.rs similarity index 99% rename from tests/graph.rs rename to src/tests/graph.rs index 04021cf..599e5be 100644 --- a/tests/graph.rs +++ b/src/tests/graph.rs @@ -1,9 +1,9 @@ use std::{path::Path, process::Command}; +use crate::{color, config, git, graph}; use chrono::{DateTime, Days, NaiveDate, TimeZone, Utc}; use image::{GenericImage, GenericImageView}; use rustc_hash::FxHashSet; -use serie::{color, config, git, graph}; type TestResult = Result<(), Box>; @@ -1400,13 +1400,13 @@ fn generate_and_output_graph_image>(path: P, option: &GenerateGra // Create concatenated image let (width, height) = (50, 50); - let image_width = ((width * (graph.max_pos_x as usize + 1)) + (width * 7)) as u32; + let image_width = ((width * (graph.max_pos_x + 1)) + (width * 7)) as u32; let image_height = (height * graph.commits.len()) as u32; let mut img_buf: image::ImageBuffer, Vec> = image::ImageBuffer::new(image_width, image_height); let text_renderer = text_to_png::TextRenderer::default(); - let text_x = (width * (graph.max_pos_x as usize + 1)) as u32; + let text_x = (width * (graph.max_pos_x + 1)) as u32; for (i, edges) in graph.edges.iter().enumerate() { let y = (height * i) as u32; diff --git a/tests/mailmap.rs b/src/tests/mailmap.rs similarity index 99% rename from tests/mailmap.rs rename to src/tests/mailmap.rs index eb1f622..878f7a5 100644 --- a/tests/mailmap.rs +++ b/src/tests/mailmap.rs @@ -1,6 +1,6 @@ use std::{fs, path::Path, process::Command}; -use serie::git::{self, Repository}; +use crate::git::{self, Repository}; type TestResult = Result<(), Box>; From 3b392244241791f15bc8bcff3ba42cc5f678b880 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 21 Aug 2026 08:55:27 +0900 Subject: [PATCH 2/5] Make library internals private --- src/git.rs | 10 ---------- src/graph/image.rs | 1 + src/lib.rs | 20 ++++++++++---------- src/protocol.rs | 7 ------- 4 files changed, 11 insertions(+), 27 deletions(-) diff --git a/src/git.rs b/src/git.rs index d83d98b..0271161 100644 --- a/src/git.rs +++ b/src/git.rs @@ -29,13 +29,6 @@ impl From<&str> for CommitHash { } } -#[derive(Debug, Default, Clone)] -pub enum CommitType { - #[default] - Commit, - Stash, -} - #[derive(Debug, Default, Clone)] pub struct Commit { pub commit_hash: CommitHash, @@ -48,7 +41,6 @@ pub struct Commit { pub subject: String, pub body: String, pub parent_commit_hashes: Vec, - pub commit_type: CommitType, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -325,7 +317,6 @@ fn load_all_commits( subject: parts[7].into(), body: parts[8].into(), parent_commit_hashes: parse_parent_commit_hashes(parts[9]), - commit_type: CommitType::Commit, }; commits.push(commit); @@ -375,7 +366,6 @@ fn load_all_stashes(path: &Path, mailmap: bool) -> Vec { subject: parts[7].into(), body: parts[8].into(), parent_commit_hashes: parse_parent_commit_hashes(parts[9]), - commit_type: CommitType::Stash, }; commits.push(commit); diff --git a/src/graph/image.rs b/src/graph/image.rs index 0d3326d..a450660 100644 --- a/src/graph/image.rs +++ b/src/graph/image.rs @@ -109,6 +109,7 @@ impl<'a> GraphImageManager<'a> { } } +#[cfg(test)] #[derive(Debug, Default)] pub struct GraphImage { pub images: FxHashMap, GraphRowImage>, diff --git a/src/lib.rs b/src/lib.rs index 473c108..9cabbbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ -pub mod color; -pub mod config; -pub mod git; -pub mod graph; -pub mod protocol; +mod color; +mod config; +mod git; +mod graph; +mod protocol; mod app; mod check; @@ -58,7 +58,7 @@ struct Args { #[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] #[serde(rename_all = "kebab-case")] -pub enum ImageProtocolType { +enum ImageProtocolType { Auto, Iterm, Kitty, @@ -81,7 +81,7 @@ impl From> for protocol::ImageProtocol { #[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] #[serde(rename_all = "lowercase")] -pub enum CommitOrderType { +enum CommitOrderType { Chrono, Topo, } @@ -98,7 +98,7 @@ impl From> for git::SortCommit { #[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] #[serde(rename_all = "lowercase")] -pub enum GraphWidthType { +enum GraphWidthType { Auto, Double, Single, @@ -106,7 +106,7 @@ pub enum GraphWidthType { #[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] #[serde(rename_all = "lowercase")] -pub enum GraphStyle { +enum GraphStyle { Rounded, Angular, } @@ -123,7 +123,7 @@ impl From> for graph::GraphStyle { #[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] #[serde(rename_all = "lowercase")] -pub enum InitialSelection { +enum InitialSelection { Latest, Head, } diff --git a/src/protocol.rs b/src/protocol.rs index 3c13309..bb6897a 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -67,7 +67,6 @@ impl PreparedImageCell { #[derive(Debug, Clone)] pub struct PreparedImage { cells: Vec, - cell_width: usize, upload_data: Option, } @@ -76,10 +75,6 @@ impl PreparedImage { &self.cells } - pub fn cell_width(&self) -> usize { - self.cell_width - } - pub fn take_upload_data(&mut self) -> Option { self.upload_data.take() } @@ -109,7 +104,6 @@ impl ImageProtocol { } PreparedImage { cells, - cell_width, upload_data: None, } } @@ -516,7 +510,6 @@ fn kitty_unicode_prepare( PreparedImage { cells, - cell_width, upload_data: Some(upload_symbol), } } From eb8ed06c2cfe595bdd6270d639f675c538e60593 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 21 Aug 2026 08:58:02 +0900 Subject: [PATCH 3/5] Make serie a binary-only crate --- CONTRIBUTING.md | 2 +- src/lib.rs | 222 ----------------------------------------------- src/main.rs | 223 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 222 insertions(+), 225 deletions(-) delete mode 100644 src/lib.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5120762..9fd2a5f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,7 +52,7 @@ We use [GitHub Actions](https://github.com/lusingander/serie/blob/master/.github Improvements to the commit graph are welcome. -Tests for the commit graph are conducted in [./tests/graph.rs](./tests/graph.rs). +Tests for the commit graph are conducted in [./src/tests/graph.rs](./src/tests/graph.rs). Running the tests will output images and the test repository to `./out/graph`. If you add new test cases, please add these images under `./tests/graph/`. diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 9cabbbf..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,222 +0,0 @@ -mod color; -mod config; -mod git; -mod graph; -mod protocol; - -mod app; -mod check; -mod event; -mod external; -mod keybind; -mod view; -mod widget; - -#[cfg(test)] -#[path = "tests/graph.rs"] -mod graph_tests; - -#[cfg(test)] -#[path = "tests/mailmap.rs"] -mod mailmap_tests; - -use std::{path::Path, rc::Rc}; - -use app::{App, Ret}; -use clap::{Parser, ValueEnum}; -use graph::GraphImageManager; -use serde::Deserialize; - -/// Serie - A rich git commit graph in your terminal, like magic 📚 -#[derive(Parser)] -#[command(version)] -struct Args { - /// Maximum number of commits to render - #[arg(short = 'n', long, value_name = "NUMBER")] - max_count: Option, - - /// Image protocol to render graph [default: auto] - #[arg(short, long, value_name = "TYPE")] - protocol: Option, - - /// Commit ordering algorithm [default: chrono] - #[arg(short, long, value_name = "TYPE")] - order: Option, - - /// Commit graph image cell width [default: auto] - #[arg(short, long, value_name = "TYPE")] - graph_width: Option, - - /// Commit graph image edge style [default: rounded] - #[arg(short = 's', long, value_name = "TYPE")] - graph_style: Option, - - /// Initial selection of commit [default: latest] - #[arg(short, long, value_name = "TYPE")] - initial_selection: Option, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] -#[serde(rename_all = "kebab-case")] -enum ImageProtocolType { - Auto, - Iterm, - Kitty, - KittyUnicode, -} - -impl From> for protocol::ImageProtocol { - fn from(protocol: Option) -> Self { - match protocol { - Some(ImageProtocolType::Auto) => protocol::auto_detect(), - Some(ImageProtocolType::Iterm) => protocol::ImageProtocol::Iterm2, - Some(ImageProtocolType::Kitty) => protocol::ImageProtocol::Kitty, - Some(ImageProtocolType::KittyUnicode) => protocol::ImageProtocol::KittyUnicode { - tmux: protocol::detect_tmux(), - }, - None => protocol::auto_detect(), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] -#[serde(rename_all = "lowercase")] -enum CommitOrderType { - Chrono, - Topo, -} - -impl From> for git::SortCommit { - fn from(order: Option) -> Self { - match order { - Some(CommitOrderType::Chrono) => git::SortCommit::Chronological, - Some(CommitOrderType::Topo) => git::SortCommit::Topological, - None => git::SortCommit::Chronological, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] -#[serde(rename_all = "lowercase")] -enum GraphWidthType { - Auto, - Double, - Single, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] -#[serde(rename_all = "lowercase")] -enum GraphStyle { - Rounded, - Angular, -} - -impl From> for graph::GraphStyle { - fn from(style: Option) -> Self { - match style { - Some(GraphStyle::Rounded) => graph::GraphStyle::Rounded, - Some(GraphStyle::Angular) => graph::GraphStyle::Angular, - None => graph::GraphStyle::Rounded, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] -#[serde(rename_all = "lowercase")] -enum InitialSelection { - Latest, - Head, -} - -impl From> for app::InitialSelection { - fn from(selection: Option) -> Self { - match selection { - Some(InitialSelection::Latest) => app::InitialSelection::Latest, - Some(InitialSelection::Head) => app::InitialSelection::Head, - None => app::InitialSelection::Latest, - } - } -} - -pub type Result = std::result::Result>; - -pub fn run() -> Result<()> { - let args = Args::parse(); - let (core_config, ui_config, graph_config, color_theme, keybind_patch) = config::load()?; - let keybind = keybind::KeyBind::new(keybind_patch); - - let max_count = args.max_count; - let image_protocol = args.protocol.or(core_config.option.protocol).into(); - let order = args.order.or(core_config.option.order).into(); - let graph_width = args.graph_width.or(core_config.option.graph_width); - let graph_style = args.graph_style.or(core_config.option.graph_style).into(); - let graph_image_width_mode = graph_config.row_image_width; - let initial_selection = args - .initial_selection - .or(core_config.option.initial_selection) - .into(); - let mailmap = core_config.git.mailmap; - - let graph_color_set = color::GraphColorSet::new(&graph_config.color); - - let ctx = Rc::new(app::AppContext { - keybind, - core_config, - ui_config, - color_theme, - image_protocol, - }); - - let ec = event::EventController::init(); - let mut refresh_view_context = None; - let mut terminal = None; - - let ret = loop { - let repository = git::Repository::load(Path::new("."), order, max_count, mailmap)?; - - let graph = graph::calc_graph(&repository); - - let cell_width_type = check::decide_cell_width_type(&graph, graph_width)?; - - let graph_image_manager = GraphImageManager::new( - &graph, - &graph_color_set, - cell_width_type, - graph_style, - graph_image_width_mode, - image_protocol, - ); - - if terminal.is_none() { - terminal = Some(ratatui::init()); - } - - let mut app = App::new( - &repository, - graph_image_manager, - &graph, - &graph_color_set, - cell_width_type, - initial_selection, - ctx.clone(), - &ec, - refresh_view_context, - ); - - match app.run(terminal.as_mut().unwrap()) { - Ok(Ret::Quit) => { - break Ok(()); - } - Ok(Ret::Refresh(request)) => { - refresh_view_context = Some(request.context); - continue; - } - Err(e) => { - break Err(e); - } - } - }; - - ratatui::restore(); - ret.map_err(Into::into) -} diff --git a/src/main.rs b/src/main.rs index 5ab7863..33d80b5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,222 @@ -fn main() -> serie::Result<()> { - serie::run() +mod color; +mod config; +mod git; +mod graph; +mod protocol; + +mod app; +mod check; +mod event; +mod external; +mod keybind; +mod view; +mod widget; + +#[cfg(test)] +#[path = "tests/graph.rs"] +mod graph_tests; + +#[cfg(test)] +#[path = "tests/mailmap.rs"] +mod mailmap_tests; + +use std::{path::Path, rc::Rc}; + +use app::{App, Ret}; +use clap::{Parser, ValueEnum}; +use graph::GraphImageManager; +use serde::Deserialize; + +/// Serie - A rich git commit graph in your terminal, like magic 📚 +#[derive(Parser)] +#[command(version)] +struct Args { + /// Maximum number of commits to render + #[arg(short = 'n', long, value_name = "NUMBER")] + max_count: Option, + + /// Image protocol to render graph [default: auto] + #[arg(short, long, value_name = "TYPE")] + protocol: Option, + + /// Commit ordering algorithm [default: chrono] + #[arg(short, long, value_name = "TYPE")] + order: Option, + + /// Commit graph image cell width [default: auto] + #[arg(short, long, value_name = "TYPE")] + graph_width: Option, + + /// Commit graph image edge style [default: rounded] + #[arg(short = 's', long, value_name = "TYPE")] + graph_style: Option, + + /// Initial selection of commit [default: latest] + #[arg(short, long, value_name = "TYPE")] + initial_selection: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] +#[serde(rename_all = "kebab-case")] +enum ImageProtocolType { + Auto, + Iterm, + Kitty, + KittyUnicode, +} + +impl From> for protocol::ImageProtocol { + fn from(protocol: Option) -> Self { + match protocol { + Some(ImageProtocolType::Auto) => protocol::auto_detect(), + Some(ImageProtocolType::Iterm) => protocol::ImageProtocol::Iterm2, + Some(ImageProtocolType::Kitty) => protocol::ImageProtocol::Kitty, + Some(ImageProtocolType::KittyUnicode) => protocol::ImageProtocol::KittyUnicode { + tmux: protocol::detect_tmux(), + }, + None => protocol::auto_detect(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] +#[serde(rename_all = "lowercase")] +enum CommitOrderType { + Chrono, + Topo, +} + +impl From> for git::SortCommit { + fn from(order: Option) -> Self { + match order { + Some(CommitOrderType::Chrono) => git::SortCommit::Chronological, + Some(CommitOrderType::Topo) => git::SortCommit::Topological, + None => git::SortCommit::Chronological, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] +#[serde(rename_all = "lowercase")] +enum GraphWidthType { + Auto, + Double, + Single, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] +#[serde(rename_all = "lowercase")] +enum GraphStyle { + Rounded, + Angular, +} + +impl From> for graph::GraphStyle { + fn from(style: Option) -> Self { + match style { + Some(GraphStyle::Rounded) => graph::GraphStyle::Rounded, + Some(GraphStyle::Angular) => graph::GraphStyle::Angular, + None => graph::GraphStyle::Rounded, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)] +#[serde(rename_all = "lowercase")] +enum InitialSelection { + Latest, + Head, +} + +impl From> for app::InitialSelection { + fn from(selection: Option) -> Self { + match selection { + Some(InitialSelection::Latest) => app::InitialSelection::Latest, + Some(InitialSelection::Head) => app::InitialSelection::Head, + None => app::InitialSelection::Latest, + } + } +} + +type Result = std::result::Result>; + +fn main() -> Result<()> { + let args = Args::parse(); + let (core_config, ui_config, graph_config, color_theme, keybind_patch) = config::load()?; + let keybind = keybind::KeyBind::new(keybind_patch); + + let max_count = args.max_count; + let image_protocol = args.protocol.or(core_config.option.protocol).into(); + let order = args.order.or(core_config.option.order).into(); + let graph_width = args.graph_width.or(core_config.option.graph_width); + let graph_style = args.graph_style.or(core_config.option.graph_style).into(); + let graph_image_width_mode = graph_config.row_image_width; + let initial_selection = args + .initial_selection + .or(core_config.option.initial_selection) + .into(); + let mailmap = core_config.git.mailmap; + + let graph_color_set = color::GraphColorSet::new(&graph_config.color); + + let ctx = Rc::new(app::AppContext { + keybind, + core_config, + ui_config, + color_theme, + image_protocol, + }); + + let ec = event::EventController::init(); + let mut refresh_view_context = None; + let mut terminal = None; + + let ret = loop { + let repository = git::Repository::load(Path::new("."), order, max_count, mailmap)?; + + let graph = graph::calc_graph(&repository); + + let cell_width_type = check::decide_cell_width_type(&graph, graph_width)?; + + let graph_image_manager = GraphImageManager::new( + &graph, + &graph_color_set, + cell_width_type, + graph_style, + graph_image_width_mode, + image_protocol, + ); + + if terminal.is_none() { + terminal = Some(ratatui::init()); + } + + let mut app = App::new( + &repository, + graph_image_manager, + &graph, + &graph_color_set, + cell_width_type, + initial_selection, + ctx.clone(), + &ec, + refresh_view_context, + ); + + match app.run(terminal.as_mut().unwrap()) { + Ok(Ret::Quit) => { + break Ok(()); + } + Ok(Ret::Refresh(request)) => { + refresh_view_context = Some(request.context); + continue; + } + Err(e) => { + break Err(e); + } + } + }; + + ratatui::restore(); + ret.map_err(Into::into) } From a666202a7c947db4fc4880b66bab004390945a2a Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 21 Aug 2026 09:02:23 +0900 Subject: [PATCH 4/5] Organize imports --- src/main.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 33d80b5..31e3d0d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,13 @@ -mod color; -mod config; -mod git; -mod graph; -mod protocol; - mod app; mod check; +mod color; +mod config; mod event; mod external; +mod git; +mod graph; mod keybind; +mod protocol; mod view; mod widget; From e35fbcd18ce19dae29e6c2f71ed078445d183a75 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 21 Aug 2026 09:06:57 +0900 Subject: [PATCH 5/5] Move GraphImage struct to test file --- src/graph/image.rs | 6 ------ src/tests/graph.rs | 17 +++++++++++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/graph/image.rs b/src/graph/image.rs index a450660..be2678f 100644 --- a/src/graph/image.rs +++ b/src/graph/image.rs @@ -109,12 +109,6 @@ impl<'a> GraphImageManager<'a> { } } -#[cfg(test)] -#[derive(Debug, Default)] -pub struct GraphImage { - pub images: FxHashMap, GraphRowImage>, -} - pub struct GraphRowImage { pub bytes: Vec, pub cell_count: usize, diff --git a/src/tests/graph.rs b/src/tests/graph.rs index 599e5be..d454931 100644 --- a/src/tests/graph.rs +++ b/src/tests/graph.rs @@ -1,9 +1,13 @@ use std::{path::Path, process::Command}; -use crate::{color, config, git, graph}; use chrono::{DateTime, Days, NaiveDate, TimeZone, Utc}; use image::{GenericImage, GenericImageView}; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; + +use crate::{ + color, config, git, + graph::{self, Edge, GraphRowImage}, +}; type TestResult = Result<(), Box>; @@ -1455,12 +1459,17 @@ fn generate_and_output_graph_image>(path: P, option: &GenerateGra .unwrap(); } +#[derive(Debug, Default)] +pub struct GraphImage { + pub images: FxHashMap, GraphRowImage>, +} + fn build_graph_image( graph: &graph::Graph<'_>, image_params: &graph::ImageParams, drawing_pixels: &graph::DrawingPixels, graph_style: graph::GraphStyle, -) -> graph::GraphImage { +) -> GraphImage { let graph_row_sources: FxHashSet<(usize, &Vec)> = graph .commits .iter() @@ -1488,7 +1497,7 @@ fn build_graph_image( }) .collect(); - graph::GraphImage { images } + GraphImage { images } } fn create_output_dirs(path: &str) {