From 452671d272e049263030cacc9ea1afba56beb926 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 10:28:29 -0600 Subject: [PATCH 1/6] add `time-ent` tool to measure runtime of different ent commands --- tools/time-ent | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100755 tools/time-ent diff --git a/tools/time-ent b/tools/time-ent new file mode 100755 index 0000000..366a02c --- /dev/null +++ b/tools/time-ent @@ -0,0 +1,43 @@ +#!/bin/bash +# +# * Create a temporary ent issue database branch based on a specific +# commit in `entomologist-data`. +# +# * Perform some ent operations on this temporary branch and measure +# the runtime. +# +# * Clean up by deleteting the temporary branch. + +set -e +#set -x + + +# This is a commit in the `entomologist-data` branch that we're somewhat +# arbitrarily using here to time different `ent` operations. +TEST_COMMIT=a33f1165d77571d770f1a1021afe4c07360247f0 + +# This is the branch that we create from the above commit and test our +# `ent` operations on. We'll delete this branch when we're done with +# the tests. +TEST_BRANCH=$(mktemp --dry-run entomologist-data-XXXXXXXX) + + +function time_ent() { + echo timing: ent "$@" + time -p ent -b "${TEST_BRANCH}" "$@" + echo +} + + +git branch "${TEST_BRANCH}" "${TEST_COMMIT}" + +time_ent tag 7e2a3a59fb6b77403ff1035255367607 +time_ent tag 7e2a3a59fb6b77403ff1035255367607 new-tag + +time_ent assign 7e2a3a59fb6b77403ff1035255367607 +time_ent assign 7e2a3a59fb6b77403ff1035255367607 new-user + +time_ent done-time 7e2a3a59fb6b77403ff1035255367607 +time_ent done-time 7e2a3a59fb6b77403ff1035255367607 2025-04-01T01:23:45-06:00 + +git branch -D "${TEST_BRANCH}" From cc1b3783468a9d29aad2f620105deed53a967a83 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 10:01:31 -0600 Subject: [PATCH 2/6] ent tag: speed up adding/removing tag --- src/bin/ent/main.rs | 75 ++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 549d16d..d3975c3 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -445,50 +445,47 @@ fn handle_command( } } - Commands::Tag { issue_id, tag } => { - let issues = entomologist::database::read_issues_database(issues_database_source)?; - let Some(issue) = issues.issues.get(issue_id) else { - return Err(anyhow::anyhow!("issue {} not found", issue_id)); - }; - match tag { - Some(tag) => { - // Add or remove tag. - let issues_database = entomologist::database::make_issues_database( - issues_database_source, - entomologist::database::IssuesDatabaseAccess::ReadWrite, - )?; - let mut issues = - entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; - let Some(issue) = issues.get_mut_issue(issue_id) else { - return Err(anyhow::anyhow!("issue {} not found", issue_id)); - }; - if tag.len() == 0 { - return Err(anyhow::anyhow!("invalid zero-length tag")); - } - if tag.chars().nth(0).unwrap() == '-' { - let tag = &tag[1..]; - issue.remove_tag(tag)?; - } else { - issue.add_tag(tag)?; - } + Commands::Tag { issue_id, tag } => match tag { + Some(tag) => { + // Add or remove tag. + if tag.len() == 0 { + return Err(anyhow::anyhow!("invalid zero-length tag")); } - None => { - // Just list the tags. - match &issue.tags.len() { - 0 => println!("no tags"), - _ => { - // Could use `format!(" {:?}", issue.tags)` - // here, but that results in `["tag1", "TAG2", - // "i-am-also-a-tag"]` and i don't want the - // double-quotes around each tag. - for tag in &issue.tags { - println!("{}", tag); - } + let issues_database = entomologist::database::make_issues_database( + issues_database_source, + entomologist::database::IssuesDatabaseAccess::ReadWrite, + )?; + let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; + let Some(issue) = issues.get_mut_issue(issue_id) else { + return Err(anyhow::anyhow!("issue {} not found", issue_id)); + }; + if tag.chars().nth(0).unwrap() == '-' { + let tag = &tag[1..]; + issue.remove_tag(tag)?; + } else { + issue.add_tag(tag)?; + } + } + None => { + // Just list the tags. + let issues = entomologist::database::read_issues_database(issues_database_source)?; + let Some(issue) = issues.issues.get(issue_id) else { + return Err(anyhow::anyhow!("issue {} not found", issue_id)); + }; + match &issue.tags.len() { + 0 => println!("no tags"), + _ => { + // Could use `format!(" {:?}", issue.tags)` + // here, but that results in `["tag1", "TAG2", + // "i-am-also-a-tag"]` and i don't want the + // double-quotes around each tag. + for tag in &issue.tags { + println!("{}", tag); } } } } - } + }, Commands::DoneTime { issue_id, From e2a7c81a132607b1430a32d206d3c6c59a2502a0 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 10:08:12 -0600 Subject: [PATCH 3/6] ent assign: speed up setting of assignee --- src/bin/ent/main.rs | 58 +++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index d3975c3..8a947e5 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -415,35 +415,37 @@ fn handle_command( Commands::Assign { issue_id, new_assignee, - } => { - let issues = entomologist::database::read_issues_database(issues_database_source)?; - let Some(original_issue) = issues.issues.get(issue_id) else { - return Err(anyhow::anyhow!("issue {} not found", issue_id)); - }; - let old_assignee: String = match &original_issue.assignee { - Some(assignee) => assignee.clone(), - None => String::from("None"), - }; - println!("issue: {}", issue_id); - match new_assignee { - Some(new_assignee) => { - let issues_database = entomologist::database::make_issues_database( - issues_database_source, - entomologist::database::IssuesDatabaseAccess::ReadWrite, - )?; - let mut issues = - entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; - let Some(issue) = issues.get_mut_issue(issue_id) else { - return Err(anyhow::anyhow!("issue {} not found", issue_id)); - }; - println!("assignee: {} -> {}", old_assignee, new_assignee); - issue.set_assignee(new_assignee)?; - } - None => { - println!("assignee: {}", old_assignee); - } + } => match new_assignee { + Some(new_assignee) => { + let issues_database = entomologist::database::make_issues_database( + issues_database_source, + entomologist::database::IssuesDatabaseAccess::ReadWrite, + )?; + let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; + let Some(issue) = issues.get_mut_issue(issue_id) else { + return Err(anyhow::anyhow!("issue {} not found", issue_id)); + }; + let old_assignee: String = match &issue.assignee { + Some(assignee) => assignee.clone(), + None => String::from("None"), + }; + issue.set_assignee(new_assignee)?; + println!("issue: {}", issue_id); + println!("assignee: {} -> {}", old_assignee, new_assignee); } - } + None => { + let issues = entomologist::database::read_issues_database(issues_database_source)?; + let Some(original_issue) = issues.issues.get(issue_id) else { + return Err(anyhow::anyhow!("issue {} not found", issue_id)); + }; + let old_assignee: String = match &original_issue.assignee { + Some(assignee) => assignee.clone(), + None => String::from("None"), + }; + println!("issue: {}", issue_id); + println!("assignee: {}", old_assignee); + } + }, Commands::Tag { issue_id, tag } => match tag { Some(tag) => { From def729d43a761d07a1a3851cdb87ca663a167a2a Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 10:12:09 -0600 Subject: [PATCH 4/6] ent done-time: speed up setting of done-time --- src/bin/ent/main.rs | 59 ++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 8a947e5..e071233 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -492,38 +492,37 @@ fn handle_command( Commands::DoneTime { issue_id, done_time, - } => { - let issues = entomologist::database::read_issues_database(issues_database_source)?; - let Some(issue) = issues.issues.get(issue_id) else { - return Err(anyhow::anyhow!("issue {} not found", issue_id)); - }; - match done_time { - Some(done_time) => { - // Add or remove tag. - let issues_database = entomologist::database::make_issues_database( - issues_database_source, - entomologist::database::IssuesDatabaseAccess::ReadWrite, - )?; - let mut issues = - entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; - let Some(issue) = issues.get_mut_issue(issue_id) else { - return Err(anyhow::anyhow!("issue {} not found", issue_id)); - }; - let done_time = match chrono::DateTime::parse_from_rfc3339(done_time) { - Ok(done_time) => done_time.with_timezone(&chrono::Local), - Err(e) => { - eprintln!("failed to parse done-time from {}", done_time); - return Err(e.into()); - } - }; - issue.set_done_time(done_time)?; - } - None => match &issue.done_time { + } => match done_time { + Some(done_time) => { + // Add or remove tag. + let issues_database = entomologist::database::make_issues_database( + issues_database_source, + entomologist::database::IssuesDatabaseAccess::ReadWrite, + )?; + let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; + let Some(issue) = issues.get_mut_issue(issue_id) else { + return Err(anyhow::anyhow!("issue {} not found", issue_id)); + }; + let done_time = match chrono::DateTime::parse_from_rfc3339(done_time) { + Ok(done_time) => done_time.with_timezone(&chrono::Local), + Err(e) => { + eprintln!("failed to parse done-time from {}", done_time); + return Err(e.into()); + } + }; + issue.set_done_time(done_time)?; + } + None => { + let issues = entomologist::database::read_issues_database(issues_database_source)?; + let Some(issue) = issues.issues.get(issue_id) else { + return Err(anyhow::anyhow!("issue {} not found", issue_id)); + }; + match &issue.done_time { Some(done_time) => println!("done_time: {}", done_time), None => println!("None"), - }, - }; - } + }; + } + }, Commands::Depend { issue_id, From c15736259c445311120bbb52e24175e745f34d16 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 13:04:54 -0600 Subject: [PATCH 5/6] add git::git_log_oldest_author_timestamp(), saves us one `git log` This cuts about 30% off the time to read the issues from entomologist-data. --- src/git.rs | 40 ++++++++++++++++++++++++++++++++++++++++ src/issue.rs | 3 +-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/git.rs b/src/git.rs index 4a50d57..3a03bac 100644 --- a/src/git.rs +++ b/src/git.rs @@ -395,6 +395,46 @@ pub fn git_log_oldest_author(path: &std::path::Path) -> Result Ok(String::from(author_last)) } +pub fn git_log_oldest_author_timestamp( + path: &std::path::Path, +) -> Result<(String, chrono::DateTime), GitError> { + let mut git_dir = std::path::PathBuf::from(path); + git_dir.pop(); + let result = std::process::Command::new("git") + .args([ + "log", + "--pretty=format:%at %an <%ae>", + "--", + &path + .file_name() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))? + .to_string_lossy(), + ]) + .current_dir(&git_dir) + .output()?; + if !result.status.success() { + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); + return Err(GitError::Oops); + } + + let raw_output_str = String::from_utf8_lossy(&result.stdout); + let Some(raw_output_last) = raw_output_str.split("\n").last() else { + return Err(GitError::Oops); + }; + let Some(index) = raw_output_last.find(' ') else { + return Err(GitError::Oops); + }; + let author_str = &raw_output_last[index + 1..]; + let timestamp_str = &raw_output_last[0..index]; + let timestamp_i64 = timestamp_str.parse::()?; + let timestamp = chrono::DateTime::from_timestamp(timestamp_i64, 0) + .unwrap() + .with_timezone(&chrono::Local); + + Ok((String::from(author_str), timestamp)) +} + pub fn create_orphan_branch(branch: &str) -> Result<(), GitError> { { let tmp_worktree = tempfile::tempdir().unwrap(); diff --git a/src/issue.rs b/src/issue.rs index aafb4e2..e3bf9d3 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -166,8 +166,7 @@ impl Issue { Err(IssueError::IdError)? }; - let author = crate::git::git_log_oldest_author(dir)?; - let creation_time = crate::git::git_log_oldest_timestamp(dir)?; + let (author, creation_time) = crate::git::git_log_oldest_author_timestamp(dir)?; Ok(Self { id, From eb7ac21ac85feae7b28861cb4ecf76b84b396d58 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 13:13:59 -0600 Subject: [PATCH 6/6] half as many `git log` calls when reading a comment --- src/comment.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/comment.rs b/src/comment.rs index 17324b3..e042f63 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -52,8 +52,8 @@ impl Comment { return Err(CommentError::CommentParseError); }; - let author = crate::git::git_log_oldest_author(comment_dir)?; - let creation_time = crate::git::git_log_oldest_timestamp(comment_dir)?; + let (author, creation_time) = crate::git::git_log_oldest_author_timestamp(comment_dir)?; + let dir = std::path::PathBuf::from(comment_dir); Ok(Self {