From 63b2118ce7d2741dac5b11da6d85964fd6c98b13 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 3 Jul 2025 08:41:20 -0600 Subject: [PATCH 001/489] add a README --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ca1a4e --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +This is a distributed, collaborative bug tracker, backed by git. From b3a7ef3f14af781fbbbbd250f7a00738b3d2218d Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 3 Jul 2025 08:42:20 -0600 Subject: [PATCH 002/489] cargo init --lib --- .gitignore | 1 + Cargo.toml | 6 ++++++ src/lib.rs | 14 ++++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ba3bd7b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "entomologist" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} From c3fc87da3de60479b2352aebdfe15720d121cece Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 3 Jul 2025 11:59:28 -0600 Subject: [PATCH 003/489] remove dead code from lib.rs --- src/lib.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b93cf3f..8b13789 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} From 07e9160cb5ed16de29ccc8844c3cd10755141fdb Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 3 Jul 2025 16:50:26 -0600 Subject: [PATCH 004/489] git ignore Cargo.lock --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ea8c4bf..96ef6c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +Cargo.lock From b9979f5e9ecee98ae16a3d571c535077678262f0 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 3 Jul 2025 12:14:26 -0600 Subject: [PATCH 005/489] start adding Issue struct This abstracts a single issue. --- Cargo.toml | 1 + src/issue.rs | 109 ++++++++++++++++++ src/lib.rs | 2 +- .../description | 4 + .../title | 1 + .../state | 1 + .../title | 1 + 7 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/issue.rs create mode 100644 test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/description create mode 100644 test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/title create mode 100644 test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/state create mode 100644 test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/title diff --git a/Cargo.toml b/Cargo.toml index ba3bd7b..905a9b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] +thiserror = "2.0.11" diff --git a/src/issue.rs b/src/issue.rs new file mode 100644 index 0000000..6dde005 --- /dev/null +++ b/src/issue.rs @@ -0,0 +1,109 @@ +use std::str::FromStr; + +#[derive(Debug, PartialEq, serde::Deserialize)] +/// These are the states an issue can be in. +pub enum State { + New, + Backlog, + InProgress, + Done, + WontDo, +} + +#[derive(Debug, PartialEq)] +pub struct Issue { + pub title: String, + pub description: Option, + pub state: State, +} + +#[derive(Debug, thiserror::Error)] +pub enum ReadIssueError { + #[error(transparent)] + StdIoError(#[from] std::io::Error), + #[error("Failed to parse issue")] + IssueParseError, +} + +impl FromStr for State { + type Err = ReadIssueError; + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + if s == "new" { + Ok(State::New) + } else if s == "backlog" { + Ok(State::Backlog) + } else if s == "inprogress" { + Ok(State::InProgress) + } else if s == "done" { + Ok(State::Done) + } else if s == "wontdo" { + Ok(State::WontDo) + } else { + Err(ReadIssueError::IssueParseError) + } + } +} + +impl Issue { + pub fn new_from_dir(dir: &std::path::Path) -> Result { + let mut title: Option = None; + let mut description: Option = None; + let mut state = State::New; // default state, if not specified in the issue + + for direntry in dir.read_dir()? { + if let Ok(direntry) = direntry { + let file_name = direntry.file_name(); + if file_name == "title" { + title = Some(std::fs::read_to_string(direntry.path())?.trim().into()); + } else if file_name == "description" { + description = Some(std::fs::read_to_string(direntry.path())?); + } else if file_name == "state" { + let state_string = std::fs::read_to_string(direntry.path())?; + state = State::from_str(state_string.trim())?; + } else { + println!("ignoring unknown file in issue directory: {:?}", file_name); + } + } + } + + if title == None { + return Err(ReadIssueError::IssueParseError); + } + + Ok(Self { + title: title.unwrap(), + description: description, + state: state, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn read_issue_0() { + let issue_dir = std::path::Path::new("test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/"); + let issue = Issue::new_from_dir(issue_dir).unwrap(); + let expected = Issue { + title: String::from("this is the title of my issue"), + description: Some(String::from("This is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n")), + state: State::New, + }; + assert_eq!(issue, expected); + } + + #[test] + fn read_issue_1() { + let issue_dir = std::path::Path::new("test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/"); + let issue = Issue::new_from_dir(issue_dir).unwrap(); + let expected = Issue { + title: String::from("minimal"), + description: None, + state: State::InProgress, + }; + assert_eq!(issue, expected); + } +} diff --git a/src/lib.rs b/src/lib.rs index 8b13789..d93d369 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1 @@ - +pub mod issue; diff --git a/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/description b/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/description new file mode 100644 index 0000000..3db0fcf --- /dev/null +++ b/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/description @@ -0,0 +1,4 @@ +This is the description of my issue. +It is multiple lines. +* Arbitrary contents +* But let's use markdown by convention diff --git a/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/title b/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/title new file mode 100644 index 0000000..c9c2379 --- /dev/null +++ b/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/title @@ -0,0 +1 @@ +this is the title of my issue diff --git a/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/state b/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/state new file mode 100644 index 0000000..0737713 --- /dev/null +++ b/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/state @@ -0,0 +1 @@ +inprogress diff --git a/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/title b/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/title new file mode 100644 index 0000000..dd1a932 --- /dev/null +++ b/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/title @@ -0,0 +1 @@ +minimal From 16c6288cee4fcca178ef129952865e171e25a88a Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 3 Jul 2025 11:59:44 -0600 Subject: [PATCH 006/489] start adding Issues struct This holds everything there is to know about everything, for now that's all issues but in the future there might be more? --- Cargo.toml | 2 + src/issues.rs | 125 ++++++++++++++++++ src/lib.rs | 1 + .../state | 1 + .../title | 1 + test/0001/config.toml | 1 + .../description | 4 + .../state | 1 + .../title | 1 + 9 files changed, 137 insertions(+) create mode 100644 src/issues.rs create mode 100644 test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/state create mode 100644 test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/title create mode 100644 test/0001/config.toml create mode 100644 test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description create mode 100644 test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/state create mode 100644 test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title diff --git a/Cargo.toml b/Cargo.toml index 905a9b5..2230bb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,6 @@ version = "0.1.0" edition = "2024" [dependencies] +serde = { version = "1.0.217", features = ["derive"] } thiserror = "2.0.11" +toml = "0.8.19" diff --git a/src/issues.rs b/src/issues.rs new file mode 100644 index 0000000..b5c1c87 --- /dev/null +++ b/src/issues.rs @@ -0,0 +1,125 @@ +// Just a placeholder for now, get rid of this if we don't need it. +#[derive(Debug, PartialEq, serde::Deserialize)] +pub struct Config {} + +#[derive(Debug, PartialEq)] +pub struct Issues { + pub issues: std::collections::HashMap, + pub config: Config, +} + +#[derive(Debug, thiserror::Error)] +pub enum ReadIssuesError { + #[error(transparent)] + StdIoError(#[from] std::io::Error), + #[error("Failed to parse issue")] + IssueParseError(#[from] crate::issue::ReadIssueError), + #[error("cannot handle filename")] + FilenameError(std::ffi::OsString), + #[error(transparent)] + TomlDeserializeError(#[from] toml::de::Error), +} + +impl Issues { + pub fn new() -> Self { + Self { + issues: std::collections::HashMap::new(), + config: Config {}, + } + } + + pub fn add_issue(&mut self, uuid: String, issue: crate::issue::Issue) { + self.issues.insert(uuid, issue); + } + + fn parse_config(&mut self, config_path: &std::path::Path) -> Result<(), ReadIssuesError> { + let config_contents = std::fs::read_to_string(config_path)?; + let config: Config = toml::from_str(&config_contents)?; + self.config = config; + Ok(()) + } + + pub fn new_from_dir(dir: &std::path::Path) -> Result { + let mut issues = Self::new(); + + for direntry in dir.read_dir()? { + if let Ok(direntry) = direntry { + if direntry.metadata()?.is_dir() { + let uuid = match direntry.file_name().into_string() { + Ok(uuid) => uuid, + Err(orig_string) => { + return Err(ReadIssuesError::FilenameError(orig_string)) + } + }; + let issue = crate::issue::Issue::new_from_dir(direntry.path().as_path())?; + issues.add_issue(uuid, issue); + } else if direntry.file_name() == "config.toml" { + issues.parse_config(direntry.path().as_path())?; + } else { + println!( + "ignoring unknown file in issues directory: {:?}", + direntry.file_name() + ); + } + } + } + return Ok(issues); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn read_issues_0000() { + let issues_dir = std::path::Path::new("test/0000/"); + let issues = Issues::new_from_dir(issues_dir).unwrap(); + + let mut expected = Issues::new(); + expected.add_issue( + String::from("7792b063eef6d33e7da5dc1856750c149ba678c6"), + crate::issue::Issue { + title: String::from("minimal"), + description: None, + state: crate::issue::State::InProgress, + }, + ); + expected.add_issue( + String::from("3943fc5c173fdf41c0a22251593cd476d96e6c9f"), + crate::issue::Issue { + title: String::from("this is the title of my issue"), + description: Some(String::from("This is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n")), + state: crate::issue::State::New, + } + ); + assert_eq!(issues, expected); + } + + #[test] + fn read_issues_0001() { + let issues_dir = std::path::Path::new("test/0001/"); + let issues = Issues::new_from_dir(issues_dir).unwrap(); + + let mut expected = Issues::new(); + expected.add_issue( + String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), + crate::issue::Issue { + title: String::from("oh yeah we got titles"), + description: None, + state: crate::issue::State::Done, + }, + ); + expected.add_issue( + String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), + crate::issue::Issue { + title: String::from("issues out the wazoo"), + description: Some(String::from( + "Lots of words\nthat don't say much\nbecause this is just\na test\n", + )), + state: crate::issue::State::WontDo, + }, + ); + assert_eq!(issues, expected); + } +} diff --git a/src/lib.rs b/src/lib.rs index d93d369..713acd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,2 @@ pub mod issue; +pub mod issues; diff --git a/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/state b/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/state new file mode 100644 index 0000000..19f86f4 --- /dev/null +++ b/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/state @@ -0,0 +1 @@ +done diff --git a/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/title b/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/title new file mode 100644 index 0000000..18a1926 --- /dev/null +++ b/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/title @@ -0,0 +1 @@ +oh yeah we got titles diff --git a/test/0001/config.toml b/test/0001/config.toml new file mode 100644 index 0000000..dfc15f2 --- /dev/null +++ b/test/0001/config.toml @@ -0,0 +1 @@ +states = [ "open", "closed" ] diff --git a/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description new file mode 100644 index 0000000..010156b --- /dev/null +++ b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description @@ -0,0 +1,4 @@ +Lots of words +that don't say much +because this is just +a test diff --git a/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/state b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/state new file mode 100644 index 0000000..7f19192 --- /dev/null +++ b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/state @@ -0,0 +1 @@ +wontdo diff --git a/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title new file mode 100644 index 0000000..ab5b4a9 --- /dev/null +++ b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title @@ -0,0 +1 @@ +issues out the wazoo From d94c991eaac39dfd25fa3dd1d6317f352705693d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 5 Jul 2025 13:55:49 -0600 Subject: [PATCH 007/489] add dependency tracking to issue type --- src/issue.rs | 18 +++++++- src/issues.rs | 45 +++++++++++++++++++ .../state | 1 + .../title | 1 + .../dependencies | 2 + .../description | 3 ++ .../state | 1 + .../title | 1 + test/0002/config.toml | 1 + .../description | 4 ++ .../state | 1 + .../title | 1 + 12 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/state create mode 100644 test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/title create mode 100644 test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies create mode 100644 test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/description create mode 100644 test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/state create mode 100644 test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/title create mode 100644 test/0002/config.toml create mode 100644 test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description create mode 100644 test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/state create mode 100644 test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title diff --git a/src/issue.rs b/src/issue.rs index 6dde005..7171380 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -5,16 +5,20 @@ use std::str::FromStr; pub enum State { New, Backlog, + Blocked, InProgress, Done, WontDo, } +pub type IssueHandle = String; + #[derive(Debug, PartialEq)] pub struct Issue { pub title: String, pub description: Option, pub state: State, + pub dependencies: Option>, } #[derive(Debug, thiserror::Error)] @@ -33,6 +37,8 @@ impl FromStr for State { Ok(State::New) } else if s == "backlog" { Ok(State::Backlog) + } else if s == "blocked" { + Ok(State::Blocked) } else if s == "inprogress" { Ok(State::InProgress) } else if s == "done" { @@ -50,6 +56,7 @@ impl Issue { let mut title: Option = None; let mut description: Option = None; let mut state = State::New; // default state, if not specified in the issue + let mut dependencies: Option> = None; for direntry in dir.read_dir()? { if let Ok(direntry) = direntry { @@ -60,7 +67,13 @@ impl Issue { description = Some(std::fs::read_to_string(direntry.path())?); } else if file_name == "state" { let state_string = std::fs::read_to_string(direntry.path())?; - state = State::from_str(state_string.trim())?; + state = State::from_str(state_string.trim())?; + } else if file_name == "dependencies" { + let dep_strings = std::fs::read_to_string(direntry.path())?; + let deps: Vec = dep_strings.lines().map(|dep|{IssueHandle::from(dep)}).collect(); + if deps.len() > 0 { + dependencies = Some(deps); + } } else { println!("ignoring unknown file in issue directory: {:?}", file_name); } @@ -75,6 +88,7 @@ impl Issue { title: title.unwrap(), description: description, state: state, + dependencies, }) } } @@ -91,6 +105,7 @@ mod tests { title: String::from("this is the title of my issue"), description: Some(String::from("This is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n")), state: State::New, + dependencies: None, }; assert_eq!(issue, expected); } @@ -103,6 +118,7 @@ mod tests { title: String::from("minimal"), description: None, state: State::InProgress, + dependencies: None, }; assert_eq!(issue, expected); } diff --git a/src/issues.rs b/src/issues.rs index b5c1c87..35116e7 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -83,6 +83,7 @@ mod tests { title: String::from("minimal"), description: None, state: crate::issue::State::InProgress, + dependencies: None, }, ); expected.add_issue( @@ -91,6 +92,7 @@ mod tests { title: String::from("this is the title of my issue"), description: Some(String::from("This is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n")), state: crate::issue::State::New, + dependencies: None, } ); assert_eq!(issues, expected); @@ -108,6 +110,7 @@ mod tests { title: String::from("oh yeah we got titles"), description: None, state: crate::issue::State::Done, + dependencies: None, }, ); expected.add_issue( @@ -118,8 +121,50 @@ mod tests { "Lots of words\nthat don't say much\nbecause this is just\na test\n", )), state: crate::issue::State::WontDo, + dependencies: None, + }, + ); + assert_eq!(issues, expected); + } + + #[test] + fn read_issues_0002() { + let issues_dir = std::path::Path::new("test/0002/"); + let issues = Issues::new_from_dir(issues_dir).unwrap(); + + let mut expected = Issues::new(); + expected.add_issue( + String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), + crate::issue::Issue { + title: String::from("oh yeah we got titles"), + description: None, + state: crate::issue::State::Done, + dependencies: None, + }, + ); + expected.add_issue( + String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), + crate::issue::Issue { + title: String::from("issues out the wazoo"), + description: Some(String::from( + "Lots of words\nthat don't say much\nbecause this is just\na test\n", + )), + state: crate::issue::State::WontDo, + dependencies: None, + }, + ); + expected.add_issue( + String::from("a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7"), + crate::issue::Issue { + title: String::from("issue with dependencies"), + description: Some(String::from( + "a test has begun\nfor dependencies we seek\nintertwining life", + )), + state: crate::issue::State::WontDo, + dependencies: Some(vec![crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), crate::issue::IssueHandle::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561")]), }, ); assert_eq!(issues, expected); } } + diff --git a/test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/state b/test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/state new file mode 100644 index 0000000..19f86f4 --- /dev/null +++ b/test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/state @@ -0,0 +1 @@ +done diff --git a/test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/title b/test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/title new file mode 100644 index 0000000..18a1926 --- /dev/null +++ b/test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/title @@ -0,0 +1 @@ +oh yeah we got titles diff --git a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies new file mode 100644 index 0000000..71e4ee3 --- /dev/null +++ b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies @@ -0,0 +1,2 @@ +3fa5bfd93317ad25772680071d5ac3259cd2384f +dd79c8cfb8beeacd0460429944b4ecbe95a31561 \ No newline at end of file diff --git a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/description b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/description new file mode 100644 index 0000000..049c15f --- /dev/null +++ b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/description @@ -0,0 +1,3 @@ +a test has begun +for dependencies we seek +intertwining life \ No newline at end of file diff --git a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/state b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/state new file mode 100644 index 0000000..7f19192 --- /dev/null +++ b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/state @@ -0,0 +1 @@ +wontdo diff --git a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/title b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/title new file mode 100644 index 0000000..7c150e7 --- /dev/null +++ b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/title @@ -0,0 +1 @@ +issue with dependencies diff --git a/test/0002/config.toml b/test/0002/config.toml new file mode 100644 index 0000000..dfc15f2 --- /dev/null +++ b/test/0002/config.toml @@ -0,0 +1 @@ +states = [ "open", "closed" ] diff --git a/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description b/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description new file mode 100644 index 0000000..010156b --- /dev/null +++ b/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description @@ -0,0 +1,4 @@ +Lots of words +that don't say much +because this is just +a test diff --git a/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/state b/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/state new file mode 100644 index 0000000..7f19192 --- /dev/null +++ b/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/state @@ -0,0 +1 @@ +wontdo diff --git a/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title b/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title new file mode 100644 index 0000000..ab5b4a9 --- /dev/null +++ b/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title @@ -0,0 +1 @@ +issues out the wazoo From e8910b906a7f05eb2a22c6752994161eb181aa63 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 4 Jul 2025 13:07:10 -0600 Subject: [PATCH 008/489] add git support This mostly provides an abstraction for "ephemeral worktrees", which is a branch checked out in a worktree, to be read and maybe modified, and the worktree is deleted/pruned when we're done with it. There are also some helper functions for doing git things, the most important one creates an orphaned branch. The intent is to keep all the issues in a git branch. When we want to do anything with issues (list them, add new issues, modify an issue, etc) we check the issues branch out into an ephemeral worktree, modify the branch, and delete the worktree. --- Cargo.toml | 2 + src/git.rs | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 3 files changed, 203 insertions(+) create mode 100644 src/git.rs diff --git a/Cargo.toml b/Cargo.toml index 2230bb4..8c9d262 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] +rand = "0.9.1" serde = { version = "1.0.217", features = ["derive"] } +tempfile = "3.20.0" thiserror = "2.0.11" toml = "0.8.19" diff --git a/src/git.rs b/src/git.rs new file mode 100644 index 0000000..2b03b5a --- /dev/null +++ b/src/git.rs @@ -0,0 +1,200 @@ +use std::io::Write; + +#[derive(Debug, thiserror::Error)] +pub enum GitError { + #[error(transparent)] + StdIoError(#[from] std::io::Error), + #[error("Oops, something went wrong")] + Oops, +} + +#[derive(Debug)] +/// `Worktree` is a struct that manages a temporary directory containing +/// a checkout of a specific branch. The worktree is removed and pruned +/// when the `Worktree` struct is dropped. +pub struct Worktree { + path: tempfile::TempDir, +} + +impl Drop for Worktree { + fn drop(&mut self) { + let _result = std::process::Command::new("git") + .args(["worktree", "remove", &self.path.path().to_string_lossy()]) + .output(); + } +} + +impl Worktree { + pub fn new(branch: &str) -> Result { + let path = tempfile::tempdir()?; + let result = std::process::Command::new("git") + .args(["worktree", "add", &path.path().to_string_lossy(), branch]) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + Ok(Self { path }) + } + + pub fn path(&self) -> &std::path::Path { + self.path.as_ref() + } +} + +pub fn checkout_branch_in_worktree( + branch: &str, + worktree_dir: &std::path::Path, +) -> Result<(), GitError> { + let result = std::process::Command::new("git") + .args(["worktree", "add", &worktree_dir.to_string_lossy(), branch]) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + Ok(()) +} + +pub fn git_worktree_prune() -> Result<(), GitError> { + let result = std::process::Command::new("git") + .args(["worktree", "prune"]) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + Ok(()) +} + +pub fn git_remove_branch(branch: &str) -> Result<(), GitError> { + let result = std::process::Command::new("git") + .args(["branch", "-D", branch]) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + Ok(()) +} + +pub fn git_branch_exists(branch: &str) -> Result { + let result = std::process::Command::new("git") + .args(["show-ref", "--quiet", branch]) + .output()?; + return Ok(result.status.success()); +} + +pub fn create_orphan_branch(branch: &str) -> Result<(), GitError> { + { + let tmp_worktree = tempfile::tempdir().unwrap(); + create_orphan_branch_at_path(branch, tmp_worktree.path())?; + } + // The temp dir is now removed / cleaned up. + + let result = std::process::Command::new("git") + .args(["worktree", "prune"]) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + + Ok(()) +} + +fn create_orphan_branch_at_path( + branch: &str, + worktree_path: &std::path::Path, +) -> Result<(), GitError> { + let worktree_dir = worktree_path.to_string_lossy(); + let result = std::process::Command::new("git") + .args(["worktree", "add", "--orphan", "-b", branch, &worktree_dir]) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + + let mut readme_filename = std::path::PathBuf::from(worktree_path); + readme_filename.push("README.md"); + let mut readme = std::fs::File::create(readme_filename)?; + write!( + readme, + "This branch is used by entomologist to track issues." + )?; + + let result = std::process::Command::new("git") + .args(["add", "README.md"]) + .current_dir(worktree_path) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + + let result = std::process::Command::new("git") + .args(["commit", "-m", "create entomologist issue branch"]) + .current_dir(worktree_path) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_worktree() { + let mut p = std::path::PathBuf::new(); + { + let worktree = Worktree::new("origin/main").unwrap(); + + p.push(worktree.path()); + assert!(p.exists()); + + let mut p2 = p.clone(); + p2.push("README.md"); + assert!(p2.exists()); + } + // The temporary worktree directory is removed when the Temp variable is dropped. + assert!(!p.exists()); + } + + #[test] + fn test_create_orphan_branch() { + let rnd: u128 = rand::random(); + let mut branch = std::string::String::from("entomologist-test-branch-"); + branch.push_str(&format!("{:0x}", rnd)); + create_orphan_branch(&branch).unwrap(); + git_remove_branch(&branch).unwrap(); + } + + #[test] + fn test_branch_exists_0() { + let r = git_branch_exists("main").unwrap(); + assert_eq!(r, true); + } + + #[test] + fn test_branch_exists_1() { + let rnd: u128 = rand::random(); + let mut branch = std::string::String::from("entomologist-missing-branch-"); + branch.push_str(&format!("{:0x}", rnd)); + let r = git_branch_exists(&branch).unwrap(); + assert_eq!(r, false); + } +} diff --git a/src/lib.rs b/src/lib.rs index 713acd1..cca1c90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ +pub mod git; pub mod issue; pub mod issues; From 26c98591b52be4f32f5b767de81f1c72d5a94375 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 3 Jul 2025 12:14:33 -0600 Subject: [PATCH 009/489] start adding `ent` binary --- Cargo.toml | 2 ++ src/bin/ent/main.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/bin/ent/main.rs diff --git a/Cargo.toml b/Cargo.toml index 8c9d262..1f7941d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] +anyhow = "1.0.95" +clap = { version = "4.5.26", features = ["derive"] } rand = "0.9.1" serde = { version = "1.0.217", features = ["derive"] } tempfile = "3.20.0" diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs new file mode 100644 index 0000000..86dfa95 --- /dev/null +++ b/src/bin/ent/main.rs @@ -0,0 +1,77 @@ +use clap::Parser; + +#[derive(Debug, clap::Parser)] +#[command(version, about, long_about = None)] +struct Args { + /// Directory containing issues. + #[arg(short = 'd', long)] + issues_dir: Option, + + /// Branch containing issues. + #[arg(short = 'b', long)] + issues_branch: Option, + + /// Type of behavior/output. + #[command(subcommand)] + command: Commands, +} + +#[derive(clap::Subcommand, Debug)] +enum Commands { + /// List issues. + List, + + /// Create a new issue. + New { + title: Option, + description: Option, + }, +} + +fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { + match &args.command { + Commands::List => { + let issues = + entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; + for (uuid, issue) in issues.issues.iter() { + println!("{} {} ({:?})", uuid, issue.title, issue.state); + } + } + Commands::New { title, description } => { + println!( + "should make a new issue, title={:?}, description={:?}", + title, description + ); + } + } + + Ok(()) +} + +fn main() -> anyhow::Result<()> { + let args: Args = Args::parse(); + // println!("{:?}", args); + + if let (Some(_), Some(_)) = (&args.issues_dir, &args.issues_branch) { + return Err(anyhow::anyhow!( + "don't specify both `--issues-dir` and `--issues-branch`" + )); + } + + if let Some(dir) = &args.issues_dir { + let dir = std::path::Path::new(dir); + handle_command(&args, dir)?; + } else { + let branch = match &args.issues_branch { + Some(branch) => branch, + None => "entomologist-data", + }; + if !entomologist::git::git_branch_exists(branch)? { + entomologist::git::create_orphan_branch(branch)?; + } + let worktree = entomologist::git::Worktree::new(branch)?; + handle_command(&args, worktree.path())?; + } + + Ok(()) +} From 64022b16fa1ad1ec1569a76739c9d92944cc9b4a Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 4 Jul 2025 00:51:12 -0600 Subject: [PATCH 010/489] add a Todo file, ironically --- Todo.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Todo.md diff --git a/Todo.md b/Todo.md new file mode 100644 index 0000000..56728ad --- /dev/null +++ b/Todo.md @@ -0,0 +1,30 @@ +# To do + +* migrate this todo list into entomologist + +* teach it to work with a git branch + - unpack the branch to a directory with `git worktree ${TMPDIR} ${BRANCH}` + - operate on the issues in that worktree + - git commit the result back to ${BRANCH} + - delete and prune the worktree + +* implement `ent new` + +* implement user control over state transitions + +* implement `ent comment ${ISSUE} [-m ${MESSAGE}]` + - each issue dir has a `comments` subdir + - each comment is identified by a sha1-style uid + - each comment is a file or directory under the `${ISSUE}/comments` + - comments are ordered by ctime? + +* implement `ent edit ${ISSUE} [-t ${TITLE}] [-d ${DESCRIPTION}]` + - or would it be better to put the title and description together into a new `message`, like git commits? + +* implement `ent edit ${COMMENT}` + +* implement `ent attach ${ISSUE} ${FILE}` + - each issue has its own independent namespace for attached files + - issue description & comments can reference attached files via standard md links + +* write a manpage From 559e70077e36b1bedac9c7ee881298da02ebb456 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 5 Jul 2025 22:40:28 -0600 Subject: [PATCH 011/489] "title" is just the first line of "description" now --- src/bin/ent/main.rs | 2 +- src/issue.rs | 33 ++++++++++--------- src/issues.rs | 33 +++++++------------ .../description | 2 ++ .../title | 1 - .../description | 1 + .../title | 1 - .../description | 1 + .../description | 2 ++ .../title | 1 - .../description} | 0 .../title | 1 - .../description | 2 ++ .../title | 1 - .../description | 2 ++ .../title | 1 - 16 files changed, 40 insertions(+), 44 deletions(-) delete mode 100644 test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/title create mode 100644 test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/description delete mode 100644 test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/title create mode 100644 test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/description delete mode 100644 test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title rename test/{0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/title => 0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/description} (100%) delete mode 100644 test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/title delete mode 100644 test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/title delete mode 100644 test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 86dfa95..65e36dc 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -34,7 +34,7 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( let issues = entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; for (uuid, issue) in issues.issues.iter() { - println!("{} {} ({:?})", uuid, issue.title, issue.state); + println!("{} {} ({:?})", uuid, issue.title(), issue.state); } } Commands::New { title, description } => { diff --git a/src/issue.rs b/src/issue.rs index 7171380..939af47 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -15,8 +15,7 @@ pub type IssueHandle = String; #[derive(Debug, PartialEq)] pub struct Issue { - pub title: String, - pub description: Option, + pub description: String, pub state: State, pub dependencies: Option>, } @@ -53,7 +52,6 @@ impl FromStr for State { impl Issue { pub fn new_from_dir(dir: &std::path::Path) -> Result { - let mut title: Option = None; let mut description: Option = None; let mut state = State::New; // default state, if not specified in the issue let mut dependencies: Option> = None; @@ -61,16 +59,17 @@ impl Issue { for direntry in dir.read_dir()? { if let Ok(direntry) = direntry { let file_name = direntry.file_name(); - if file_name == "title" { - title = Some(std::fs::read_to_string(direntry.path())?.trim().into()); - } else if file_name == "description" { + if file_name == "description" { description = Some(std::fs::read_to_string(direntry.path())?); } else if file_name == "state" { let state_string = std::fs::read_to_string(direntry.path())?; - state = State::from_str(state_string.trim())?; + state = State::from_str(state_string.trim())?; } else if file_name == "dependencies" { let dep_strings = std::fs::read_to_string(direntry.path())?; - let deps: Vec = dep_strings.lines().map(|dep|{IssueHandle::from(dep)}).collect(); + let deps: Vec = dep_strings + .lines() + .map(|dep| IssueHandle::from(dep)) + .collect(); if deps.len() > 0 { dependencies = Some(deps); } @@ -80,17 +79,23 @@ impl Issue { } } - if title == None { + if description == None { return Err(ReadIssueError::IssueParseError); } Ok(Self { - title: title.unwrap(), - description: description, + description: description.unwrap(), state: state, dependencies, }) } + + pub fn title<'a>(&'a self) -> &'a str { + match self.description.find("\n") { + Some(index) => &self.description.as_str()[..index], + None => self.description.as_str(), + } + } } #[cfg(test)] @@ -102,8 +107,7 @@ mod tests { let issue_dir = std::path::Path::new("test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/"); let issue = Issue::new_from_dir(issue_dir).unwrap(); let expected = Issue { - title: String::from("this is the title of my issue"), - description: Some(String::from("This is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n")), + description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), state: State::New, dependencies: None, }; @@ -115,8 +119,7 @@ mod tests { let issue_dir = std::path::Path::new("test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/"); let issue = Issue::new_from_dir(issue_dir).unwrap(); let expected = Issue { - title: String::from("minimal"), - description: None, + description: String::from("minimal"), state: State::InProgress, dependencies: None, }; diff --git a/src/issues.rs b/src/issues.rs index 35116e7..9422ded 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -80,8 +80,7 @@ mod tests { expected.add_issue( String::from("7792b063eef6d33e7da5dc1856750c149ba678c6"), crate::issue::Issue { - title: String::from("minimal"), - description: None, + description: String::from("minimal"), state: crate::issue::State::InProgress, dependencies: None, }, @@ -89,8 +88,7 @@ mod tests { expected.add_issue( String::from("3943fc5c173fdf41c0a22251593cd476d96e6c9f"), crate::issue::Issue { - title: String::from("this is the title of my issue"), - description: Some(String::from("This is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n")), + description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), state: crate::issue::State::New, dependencies: None, } @@ -107,8 +105,7 @@ mod tests { expected.add_issue( String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), crate::issue::Issue { - title: String::from("oh yeah we got titles"), - description: None, + description: String::from("oh yeah we got titles"), state: crate::issue::State::Done, dependencies: None, }, @@ -116,10 +113,7 @@ mod tests { expected.add_issue( String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), crate::issue::Issue { - title: String::from("issues out the wazoo"), - description: Some(String::from( - "Lots of words\nthat don't say much\nbecause this is just\na test\n", - )), + description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), state: crate::issue::State::WontDo, dependencies: None, }, @@ -136,8 +130,7 @@ mod tests { expected.add_issue( String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), crate::issue::Issue { - title: String::from("oh yeah we got titles"), - description: None, + description: String::from("oh yeah we got titles\n"), state: crate::issue::State::Done, dependencies: None, }, @@ -145,10 +138,7 @@ mod tests { expected.add_issue( String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), crate::issue::Issue { - title: String::from("issues out the wazoo"), - description: Some(String::from( - "Lots of words\nthat don't say much\nbecause this is just\na test\n", - )), + description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), state: crate::issue::State::WontDo, dependencies: None, }, @@ -156,15 +146,14 @@ mod tests { expected.add_issue( String::from("a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7"), crate::issue::Issue { - title: String::from("issue with dependencies"), - description: Some(String::from( - "a test has begun\nfor dependencies we seek\nintertwining life", - )), + description: String::from("issue with dependencies\n\na test has begun\nfor dependencies we seek\nintertwining life"), state: crate::issue::State::WontDo, - dependencies: Some(vec![crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), crate::issue::IssueHandle::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561")]), + dependencies: Some(vec![ + crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), + crate::issue::IssueHandle::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), + ]), }, ); assert_eq!(issues, expected); } } - diff --git a/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/description b/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/description index 3db0fcf..e380829 100644 --- a/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/description +++ b/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/description @@ -1,3 +1,5 @@ +this is the title of my issue + This is the description of my issue. It is multiple lines. * Arbitrary contents diff --git a/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/title b/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/title deleted file mode 100644 index c9c2379..0000000 --- a/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/title +++ /dev/null @@ -1 +0,0 @@ -this is the title of my issue diff --git a/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/description b/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/description new file mode 100644 index 0000000..982085a --- /dev/null +++ b/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/description @@ -0,0 +1 @@ +minimal \ No newline at end of file diff --git a/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/title b/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/title deleted file mode 100644 index dd1a932..0000000 --- a/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/title +++ /dev/null @@ -1 +0,0 @@ -minimal diff --git a/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/description b/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/description new file mode 100644 index 0000000..c73d593 --- /dev/null +++ b/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/description @@ -0,0 +1 @@ +oh yeah we got titles \ No newline at end of file diff --git a/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description index 010156b..a65ceb6 100644 --- a/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description +++ b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description @@ -1,3 +1,5 @@ +issues out the wazoo + Lots of words that don't say much because this is just diff --git a/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title deleted file mode 100644 index ab5b4a9..0000000 --- a/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title +++ /dev/null @@ -1 +0,0 @@ -issues out the wazoo diff --git a/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/title b/test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/description similarity index 100% rename from test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/title rename to test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/description diff --git a/test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/title b/test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/title deleted file mode 100644 index 18a1926..0000000 --- a/test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/title +++ /dev/null @@ -1 +0,0 @@ -oh yeah we got titles diff --git a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/description b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/description index 049c15f..42e2ce3 100644 --- a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/description +++ b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/description @@ -1,3 +1,5 @@ +issue with dependencies + a test has begun for dependencies we seek intertwining life \ No newline at end of file diff --git a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/title b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/title deleted file mode 100644 index 7c150e7..0000000 --- a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/title +++ /dev/null @@ -1 +0,0 @@ -issue with dependencies diff --git a/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description b/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description index 010156b..a65ceb6 100644 --- a/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description +++ b/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description @@ -1,3 +1,5 @@ +issues out the wazoo + Lots of words that don't say much because this is just diff --git a/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title b/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title deleted file mode 100644 index ab5b4a9..0000000 --- a/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/title +++ /dev/null @@ -1 +0,0 @@ -issues out the wazoo From 1f4456fcaf4723810de70421bb52368cb2f079d4 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 5 Jul 2025 23:34:24 -0600 Subject: [PATCH 012/489] teach Issue to know what dir it lives in The Issue struct is a cache of files on disk. There is never an Issue without a directory to live in. This commit adds a field to Issue to track what that directory is, so that we can update those filew when we change the Issue, and commit the changes to git. --- src/issue.rs | 7 +++++++ src/issues.rs | 49 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/issue.rs b/src/issue.rs index 939af47..0f3dfd2 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -18,6 +18,10 @@ pub struct Issue { pub description: String, pub state: State, pub dependencies: Option>, + + /// This is the directory that the issue lives in. Only used + /// internally by the entomologist library. + pub dir: std::path::PathBuf, } #[derive(Debug, thiserror::Error)] @@ -87,6 +91,7 @@ impl Issue { description: description.unwrap(), state: state, dependencies, + dir: std::path::PathBuf::from(dir), }) } @@ -110,6 +115,7 @@ mod tests { description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), state: State::New, dependencies: None, + dir: std::path::PathBuf::from(issue_dir), }; assert_eq!(issue, expected); } @@ -122,6 +128,7 @@ mod tests { description: String::from("minimal"), state: State::InProgress, dependencies: None, + dir: std::path::PathBuf::from(issue_dir), }; assert_eq!(issue, expected); } diff --git a/src/issues.rs b/src/issues.rs index 9422ded..0bfda84 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -77,20 +77,30 @@ mod tests { let issues = Issues::new_from_dir(issues_dir).unwrap(); let mut expected = Issues::new(); + + let uuid = String::from("7792b063eef6d33e7da5dc1856750c149ba678c6"); + let mut dir = std::path::PathBuf::from(issues_dir); + dir.push(&uuid); expected.add_issue( - String::from("7792b063eef6d33e7da5dc1856750c149ba678c6"), + uuid, crate::issue::Issue { description: String::from("minimal"), state: crate::issue::State::InProgress, dependencies: None, + dir, }, ); + + let uuid = String::from("3943fc5c173fdf41c0a22251593cd476d96e6c9f"); + let mut dir = std::path::PathBuf::from(issues_dir); + dir.push(&uuid); expected.add_issue( - String::from("3943fc5c173fdf41c0a22251593cd476d96e6c9f"), + uuid, crate::issue::Issue { description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), state: crate::issue::State::New, dependencies: None, + dir, } ); assert_eq!(issues, expected); @@ -102,20 +112,30 @@ mod tests { let issues = Issues::new_from_dir(issues_dir).unwrap(); let mut expected = Issues::new(); + + let uuid = String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"); + let mut dir = std::path::PathBuf::from(issues_dir); + dir.push(&uuid); expected.add_issue( - String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), + uuid, crate::issue::Issue { description: String::from("oh yeah we got titles"), state: crate::issue::State::Done, dependencies: None, + dir, }, ); + + let uuid = String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"); + let mut dir = std::path::PathBuf::from(issues_dir); + dir.push(&uuid); expected.add_issue( - String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), + uuid, crate::issue::Issue { description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), state: crate::issue::State::WontDo, dependencies: None, + dir, }, ); assert_eq!(issues, expected); @@ -127,24 +147,38 @@ mod tests { let issues = Issues::new_from_dir(issues_dir).unwrap(); let mut expected = Issues::new(); + + let uuid = String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"); + let mut dir = std::path::PathBuf::from(issues_dir); + dir.push(&uuid); expected.add_issue( - String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), + uuid, crate::issue::Issue { description: String::from("oh yeah we got titles\n"), state: crate::issue::State::Done, dependencies: None, + dir, }, ); + + let uuid = String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"); + let mut dir = std::path::PathBuf::from(issues_dir); + dir.push(&uuid); expected.add_issue( - String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), + uuid, crate::issue::Issue { description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), state: crate::issue::State::WontDo, dependencies: None, + dir, }, ); + + let uuid = String::from("a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7"); + let mut dir = std::path::PathBuf::from(issues_dir); + dir.push(&uuid); expected.add_issue( - String::from("a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7"), + uuid, crate::issue::Issue { description: String::from("issue with dependencies\n\na test has begun\nfor dependencies we seek\nintertwining life"), state: crate::issue::State::WontDo, @@ -152,6 +186,7 @@ mod tests { crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), crate::issue::IssueHandle::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), ]), + dir, }, ); assert_eq!(issues, expected); From 5b1c7a52b913450bc455385b9f905eb3ec1b2493 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 5 Jul 2025 23:51:54 -0600 Subject: [PATCH 013/489] git: add git_commit_file() --- src/git.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/git.rs b/src/git.rs index 2b03b5a..bb17763 100644 --- a/src/git.rs +++ b/src/git.rs @@ -89,6 +89,41 @@ pub fn git_branch_exists(branch: &str) -> Result { return Ok(result.status.success()); } +pub fn git_commit_file(file: &std::path::Path) -> Result<(), GitError> { + let mut git_dir = std::path::PathBuf::from(file); + git_dir.pop(); + + let result = std::process::Command::new("git") + .args(["add", &file.file_name().unwrap().to_string_lossy()]) + .current_dir(&git_dir) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + + let result = std::process::Command::new("git") + .args([ + "commit", + "-m", + &format!( + "update '{}' in issue {}", + file.file_name().unwrap().to_string_lossy(), + git_dir.file_name().unwrap().to_string_lossy() + ), + ]) + .current_dir(&git_dir) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + + Ok(()) +} + pub fn create_orphan_branch(branch: &str) -> Result<(), GitError> { { let tmp_worktree = tempfile::tempdir().unwrap(); From 5e482edb5c5192efc447817f13172a08667f0fbe Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 6 Jul 2025 00:11:23 -0600 Subject: [PATCH 014/489] rename ReadIssueError to just IssueError Error handling is pretty broken in this project :-( --- src/issue.rs | 10 +++++----- src/issues.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/issue.rs b/src/issue.rs index 0f3dfd2..753454b 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -25,7 +25,7 @@ pub struct Issue { } #[derive(Debug, thiserror::Error)] -pub enum ReadIssueError { +pub enum IssueError { #[error(transparent)] StdIoError(#[from] std::io::Error), #[error("Failed to parse issue")] @@ -33,7 +33,7 @@ pub enum ReadIssueError { } impl FromStr for State { - type Err = ReadIssueError; + type Err = IssueError; fn from_str(s: &str) -> Result { let s = s.to_lowercase(); if s == "new" { @@ -49,13 +49,13 @@ impl FromStr for State { } else if s == "wontdo" { Ok(State::WontDo) } else { - Err(ReadIssueError::IssueParseError) + Err(IssueError::IssueParseError) } } } impl Issue { - pub fn new_from_dir(dir: &std::path::Path) -> Result { + pub fn new_from_dir(dir: &std::path::Path) -> Result { let mut description: Option = None; let mut state = State::New; // default state, if not specified in the issue let mut dependencies: Option> = None; @@ -84,7 +84,7 @@ impl Issue { } if description == None { - return Err(ReadIssueError::IssueParseError); + return Err(IssueError::IssueParseError); } Ok(Self { diff --git a/src/issues.rs b/src/issues.rs index 0bfda84..bb548e4 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -12,8 +12,8 @@ pub struct Issues { pub enum ReadIssuesError { #[error(transparent)] StdIoError(#[from] std::io::Error), - #[error("Failed to parse issue")] - IssueParseError(#[from] crate::issue::ReadIssueError), + #[error(transparent)] + IssueError(#[from] crate::issue::IssueError), #[error("cannot handle filename")] FilenameError(std::ffi::OsString), #[error(transparent)] From 09373cda56ecc1b00478a28a55a1cb7e8a76d341 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 6 Jul 2025 00:12:22 -0600 Subject: [PATCH 015/489] add `ent new` --- src/bin/ent/main.rs | 21 ++++++++++-------- src/issue.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 65e36dc..e0dca3c 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -22,10 +22,7 @@ enum Commands { List, /// Create a new issue. - New { - title: Option, - description: Option, - }, + New { description: Option }, } fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { @@ -37,11 +34,17 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( println!("{} {} ({:?})", uuid, issue.title(), issue.state); } } - Commands::New { title, description } => { - println!( - "should make a new issue, title={:?}, description={:?}", - title, description - ); + Commands::New { + description: Some(description), + } => { + let mut issue = entomologist::issue::Issue::new(issues_dir)?; + issue.set_description(description)?; + println!("created new issue '{}'", issue.title()); + } + Commands::New { description: None } => { + let mut issue = entomologist::issue::Issue::new(issues_dir)?; + issue.edit_description()?; + println!("created new issue '{}'", issue.title()); } } diff --git a/src/issue.rs b/src/issue.rs index 753454b..b91d3c6 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -1,3 +1,4 @@ +use std::io::Write; use std::str::FromStr; #[derive(Debug, PartialEq, serde::Deserialize)] @@ -30,6 +31,10 @@ pub enum IssueError { StdIoError(#[from] std::io::Error), #[error("Failed to parse issue")] IssueParseError, + #[error("Failed to run git")] + GitError(#[from] crate::git::GitError), + #[error("Failed to run editor")] + EditorError, } impl FromStr for State { @@ -95,6 +100,53 @@ impl Issue { }) } + pub fn new(dir: &std::path::Path) -> Result { + let mut issue_dir = std::path::PathBuf::from(dir); + let rnd: u128 = rand::random(); + issue_dir.push(&format!("{:0x}", rnd)); + std::fs::create_dir(&issue_dir)?; + Ok(Self { + description: String::from(""), // FIXME: kind of bogus to use the empty string as None + state: State::New, + dependencies: None, + dir: issue_dir, + }) + } + + pub fn set_description(&mut self, description: &str) -> Result<(), IssueError> { + self.description = String::from(description); + let mut description_filename = std::path::PathBuf::from(&self.dir); + description_filename.push("description"); + let mut description_file = std::fs::File::create(&description_filename)?; + write!(description_file, "{}", description)?; + crate::git::git_commit_file(&description_filename)?; + Ok(()) + } + + pub fn read_description(&mut self) -> Result<(), IssueError> { + let mut description_filename = std::path::PathBuf::from(&self.dir); + description_filename.push("description"); + self.description = std::fs::read_to_string(description_filename)?; + Ok(()) + } + + pub fn edit_description(&mut self) -> Result<(), IssueError> { + let mut description_filename = std::path::PathBuf::from(&self.dir); + description_filename.push("description"); + let result = std::process::Command::new("vi") + .arg(&description_filename.as_mut_os_str()) + .spawn()? + .wait_with_output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(IssueError::EditorError); + } + crate::git::git_commit_file(&description_filename)?; + self.read_description()?; + Ok(()) + } + pub fn title<'a>(&'a self) -> &'a str { match self.description.find("\n") { Some(index) => &self.description.as_str()[..index], From ba0862f5a6d8f59395b5fab2a573118ab8ed4c4e Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 6 Jul 2025 00:18:36 -0600 Subject: [PATCH 016/489] add `ent edit` --- src/bin/ent/main.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index e0dca3c..3d6f80b 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -23,6 +23,9 @@ enum Commands { /// Create a new issue. New { description: Option }, + + /// Edit the description of an issue. + Edit { issue_id: String }, } fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { @@ -46,6 +49,18 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( issue.edit_description()?; println!("created new issue '{}'", issue.title()); } + Commands::Edit { issue_id } => { + let mut issues = + entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; + match issues.issues.get_mut(issue_id) { + Some(issue) => { + issue.edit_description()?; + } + None => { + println!("issue {} not found", issue_id); + } + } + } } Ok(()) From 3f2d3b1520a49b50a75b11df0891d771530f69fd Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 6 Jul 2025 00:21:25 -0600 Subject: [PATCH 017/489] add `ent show` --- src/bin/ent/main.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 3d6f80b..3561f4c 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -26,6 +26,9 @@ enum Commands { /// Edit the description of an issue. Edit { issue_id: String }, + + /// Show the full description of an issue. + Show { issue_id: String }, } fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { @@ -61,6 +64,21 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } } } + Commands::Show { issue_id } => { + let issues = + entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; + match issues.issues.get(issue_id) { + Some(issue) => { + println!("issue {}", issue_id); + println!("state {:?}", issue.state); + println!(""); + println!("{}", issue.description); + } + None => { + println!("issue {} not found", issue_id); + } + } + } } Ok(()) From 49d7422fbc2cb372a6a44641699f4a2bc18a2754 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 6 Jul 2025 00:29:32 -0600 Subject: [PATCH 018/489] create entomologist issue branch --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..2bd9d23 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +This branch is used by entomologist to track issues. \ No newline at end of file From e3bb39b7cc136d97316f72cbdb83f569de39967c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 6 Jul 2025 00:30:13 -0600 Subject: [PATCH 019/489] update 'description' in issue 8c73c9fd5bc4f551ee5069035ae6e866 --- 8c73c9fd5bc4f551ee5069035ae6e866/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 8c73c9fd5bc4f551ee5069035ae6e866/description diff --git a/8c73c9fd5bc4f551ee5069035ae6e866/description b/8c73c9fd5bc4f551ee5069035ae6e866/description new file mode 100644 index 0000000..9876137 --- /dev/null +++ b/8c73c9fd5bc4f551ee5069035ae6e866/description @@ -0,0 +1 @@ +migrate the Todo list into entomologist \ No newline at end of file From 2d03d40dc1707a2249ee5c45e57cfe4ceef9b327 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 6 Jul 2025 00:30:55 -0600 Subject: [PATCH 020/489] update 'description' in issue da435e5e298b28dc223f9dcfe62a914 --- da435e5e298b28dc223f9dcfe62a914/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 da435e5e298b28dc223f9dcfe62a914/description diff --git a/da435e5e298b28dc223f9dcfe62a914/description b/da435e5e298b28dc223f9dcfe62a914/description new file mode 100644 index 0000000..a1ba09a --- /dev/null +++ b/da435e5e298b28dc223f9dcfe62a914/description @@ -0,0 +1 @@ +add user control over state transitions \ No newline at end of file From 1b17e1e1ed9b9f746d6bafcbe7ae9581413eb329 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 6 Jul 2025 00:31:27 -0600 Subject: [PATCH 021/489] update 'description' in issue 75cefad80aacbf23fc7b9c24a75aa236 --- 75cefad80aacbf23fc7b9c24a75aa236/description | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 75cefad80aacbf23fc7b9c24a75aa236/description diff --git a/75cefad80aacbf23fc7b9c24a75aa236/description b/75cefad80aacbf23fc7b9c24a75aa236/description new file mode 100644 index 0000000..212fa34 --- /dev/null +++ b/75cefad80aacbf23fc7b9c24a75aa236/description @@ -0,0 +1,6 @@ +# implement `ent comment ${ISSUE} [-m ${MESSAGE}]` + +- each issue dir has a `comments` subdir +- each comment is identified by a sha1-style uid +- each comment is a file or directory under the `${ISSUE}/comments` +- comments are ordered by ctime? From 3f022609c6afc8e3db6f6d8fb70afba9a28ae467 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 6 Jul 2025 00:31:50 -0600 Subject: [PATCH 022/489] update 'description' in issue 7da3bd5b72de0a05936b094db5d24304 --- 7da3bd5b72de0a05936b094db5d24304/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 7da3bd5b72de0a05936b094db5d24304/description diff --git a/7da3bd5b72de0a05936b094db5d24304/description b/7da3bd5b72de0a05936b094db5d24304/description new file mode 100644 index 0000000..f23a78e --- /dev/null +++ b/7da3bd5b72de0a05936b094db5d24304/description @@ -0,0 +1 @@ +implement `ent edit ${COMMENT}` \ No newline at end of file From e915d7260200c2287c656a36d8299d4e515bc3e3 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 6 Jul 2025 00:32:09 -0600 Subject: [PATCH 023/489] update 'description' in issue 1f85dfac686d5ea2417b2b07f7e1ff01 --- 1f85dfac686d5ea2417b2b07f7e1ff01/description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 1f85dfac686d5ea2417b2b07f7e1ff01/description diff --git a/1f85dfac686d5ea2417b2b07f7e1ff01/description b/1f85dfac686d5ea2417b2b07f7e1ff01/description new file mode 100644 index 0000000..8118186 --- /dev/null +++ b/1f85dfac686d5ea2417b2b07f7e1ff01/description @@ -0,0 +1,4 @@ +# implement `ent attach ${ISSUE} ${FILE}` + +- each issue has its own independent namespace for attached files +- issue description & comments can reference attached files via standard md links From 2b186e01b2cc1f2c60f941e5e05418390e8fef91 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 6 Jul 2025 00:32:21 -0600 Subject: [PATCH 024/489] update 'description' in issue b738f2842db428df1b4aad0192a7f36c --- b738f2842db428df1b4aad0192a7f36c/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 b738f2842db428df1b4aad0192a7f36c/description diff --git a/b738f2842db428df1b4aad0192a7f36c/description b/b738f2842db428df1b4aad0192a7f36c/description new file mode 100644 index 0000000..097bfa4 --- /dev/null +++ b/b738f2842db428df1b4aad0192a7f36c/description @@ -0,0 +1 @@ +write a manpage \ No newline at end of file From 3023576fec1422cf3e43e5be15daadca8989ceda Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 6 Jul 2025 00:36:51 -0600 Subject: [PATCH 025/489] did some todo items --- Todo.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Todo.md b/Todo.md index 56728ad..9594c71 100644 --- a/Todo.md +++ b/Todo.md @@ -2,14 +2,6 @@ * migrate this todo list into entomologist -* teach it to work with a git branch - - unpack the branch to a directory with `git worktree ${TMPDIR} ${BRANCH}` - - operate on the issues in that worktree - - git commit the result back to ${BRANCH} - - delete and prune the worktree - -* implement `ent new` - * implement user control over state transitions * implement `ent comment ${ISSUE} [-m ${MESSAGE}]` @@ -18,9 +10,6 @@ - each comment is a file or directory under the `${ISSUE}/comments` - comments are ordered by ctime? -* implement `ent edit ${ISSUE} [-t ${TITLE}] [-d ${DESCRIPTION}]` - - or would it be better to put the title and description together into a new `message`, like git commits? - * implement `ent edit ${COMMENT}` * implement `ent attach ${ISSUE} ${FILE}` From d67a2fb8ef3eca6b906115f8815da840fb1701be Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 6 Jul 2025 23:09:18 -0600 Subject: [PATCH 026/489] update 'description' in issue 198a7d56a19f0579fbc04f2ee9cc234f --- 198a7d56a19f0579fbc04f2ee9cc234f/description | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 198a7d56a19f0579fbc04f2ee9cc234f/description diff --git a/198a7d56a19f0579fbc04f2ee9cc234f/description b/198a7d56a19f0579fbc04f2ee9cc234f/description new file mode 100644 index 0000000..65775af --- /dev/null +++ b/198a7d56a19f0579fbc04f2ee9cc234f/description @@ -0,0 +1,8 @@ +fix ignoring unknown file in issues directory: "README.md" +ignoring unknown file in issues directory: ".git" +b738f2842db428df1b4aad0192a7f36c write a manpage (New) +75cefad80aacbf23fc7b9c24a75aa236 # implement `ent comment ${ISSUE} [-m ${MESSAGE}]` (New) +7da3bd5b72de0a05936b094db5d24304 implement `ent edit ${COMMENT}` (New) +1f85dfac686d5ea2417b2b07f7e1ff01 # implement `ent attach ${ISSUE} ${FILE}` (New) +da435e5e298b28dc223f9dcfe62a914 add user control over state transitions (New) +8c73c9fd5bc4f551ee5069035ae6e866 migrate the Todo list into entomologist (New) emitting warnings for unknown files \ No newline at end of file From 6c26379e82683c1c3cf4efd11ed9bed4be378d88 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 6 Jul 2025 23:13:10 -0600 Subject: [PATCH 027/489] update 'description' in issue 8edf884dbde5828a30a4dccad503f28a --- 8edf884dbde5828a30a4dccad503f28a/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 8edf884dbde5828a30a4dccad503f28a/description diff --git a/8edf884dbde5828a30a4dccad503f28a/description b/8edf884dbde5828a30a4dccad503f28a/description new file mode 100644 index 0000000..213599b --- /dev/null +++ b/8edf884dbde5828a30a4dccad503f28a/description @@ -0,0 +1 @@ +add sync subcommand to sync entomologist-data branch \ No newline at end of file From 8e8db012ddec846572868ca32aef2cb70e2054c8 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 6 Jul 2025 23:13:52 -0600 Subject: [PATCH 028/489] update 'description' in issue 1ebdee0502937bf934bb0d72256dbdd1 --- 1ebdee0502937bf934bb0d72256dbdd1/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 1ebdee0502937bf934bb0d72256dbdd1/description diff --git a/1ebdee0502937bf934bb0d72256dbdd1/description b/1ebdee0502937bf934bb0d72256dbdd1/description new file mode 100644 index 0000000..e4bbac3 --- /dev/null +++ b/1ebdee0502937bf934bb0d72256dbdd1/description @@ -0,0 +1 @@ +add delete subcommand to delete ENTries (heh) \ No newline at end of file From 11996db430de68a7f130f4760530ba620d0f4a53 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 6 Jul 2025 23:16:08 -0600 Subject: [PATCH 029/489] update 'description' in issue a97c817024233be0e34536dfb1323070 --- a97c817024233be0e34536dfb1323070/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 a97c817024233be0e34536dfb1323070/description diff --git a/a97c817024233be0e34536dfb1323070/description b/a97c817024233be0e34536dfb1323070/description new file mode 100644 index 0000000..4feea71 --- /dev/null +++ b/a97c817024233be0e34536dfb1323070/description @@ -0,0 +1 @@ +update ent new to output the issue ID \ No newline at end of file From 32d4e105290446826a0081ccb4cbe7130f82672c Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 6 Jul 2025 23:17:46 -0600 Subject: [PATCH 030/489] update 'description' in issue eee4a129dacac9ddff2e50580e822cbf --- eee4a129dacac9ddff2e50580e822cbf/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 eee4a129dacac9ddff2e50580e822cbf/description diff --git a/eee4a129dacac9ddff2e50580e822cbf/description b/eee4a129dacac9ddff2e50580e822cbf/description new file mode 100644 index 0000000..b2078c9 --- /dev/null +++ b/eee4a129dacac9ddff2e50580e822cbf/description @@ -0,0 +1 @@ +ent edit fails to find file/directory \ No newline at end of file From 24df544a035192f0fac1bf6adeb95479993ebc3e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 7 Jul 2025 12:03:03 -0600 Subject: [PATCH 031/489] add install.sh script --- install.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100755 install.sh diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..9a459a3 --- /dev/null +++ b/install.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +BINFILE="${SCRIPT_DIR}/target/release/ent" +INSTALL_DIR="/usr/bin" + +cargo build --release +echo "copying ent to ${INSTALL_DIR}" +sudo cp $BINFILE $INSTALL_DIR +echo "ent installed to ${INSTALL_DIR}" From 367ece066178d0d8bd03659e2bd54c371a401dd1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 12:23:17 -0600 Subject: [PATCH 032/489] update 'description' in issue e089400e8a9e11fe9bf10d50b2f889d7 --- e089400e8a9e11fe9bf10d50b2f889d7/description | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 e089400e8a9e11fe9bf10d50b2f889d7/description diff --git a/e089400e8a9e11fe9bf10d50b2f889d7/description b/e089400e8a9e11fe9bf10d50b2f889d7/description new file mode 100644 index 0000000..1b0558e --- /dev/null +++ b/e089400e8a9e11fe9bf10d50b2f889d7/description @@ -0,0 +1,15 @@ +keep local `entomologist-data` branch in sync with `origin/entomologist-data` + +My git workflow does not use `git pull`, instead i: +1. `git fetch` +2. `git merge` or `git rebase` + +When i run `git fetch` to fetch new issues, my local +`entomologist-data` branch does not get automatically synchronized with +`origin/entomologist-data`. + +The clumsy workaround is: +1. `git fetch` +2. `git checkout entomologist-data` +3. `git merge origin/entomologist-data` +4. `git checkout ${BRANCH}` From 1e5d328ab4a1e46e013a7388575c26eb16a34367 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 7 Jul 2025 12:31:09 -0600 Subject: [PATCH 033/489] add logging crate to reduce unnecessary stdout spam --- Cargo.toml | 6 ++++++ src/bin/ent/main.rs | 6 ++++++ src/issue.rs | 6 +++++- src/issues.rs | 6 +++++- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1f7941d..fccb9c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,11 +3,17 @@ name = "entomologist" version = "0.1.0" edition = "2024" +[features] +default = [] +log = ["dep:log", "dep:simple_logger"] + [dependencies] anyhow = "1.0.95" clap = { version = "4.5.26", features = ["derive"] } +log = { version = "0.4.27", optional = true } rand = "0.9.1" serde = { version = "1.0.217", features = ["derive"] } +simple_logger = { version = "5.0.0", optional = true } tempfile = "3.20.0" thiserror = "2.0.11" toml = "0.8.19" diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 65e36dc..47fa87c 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -1,5 +1,8 @@ use clap::Parser; +#[cfg(feature = "log")] +use simple_logger; + #[derive(Debug, clap::Parser)] #[command(version, about, long_about = None)] struct Args { @@ -49,6 +52,9 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } fn main() -> anyhow::Result<()> { + #[cfg(feature = "log")] + simple_logger::SimpleLogger::new().env().init().unwrap(); + let args: Args = Args::parse(); // println!("{:?}", args); diff --git a/src/issue.rs b/src/issue.rs index 939af47..8008ea9 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -1,5 +1,8 @@ use std::str::FromStr; +#[cfg(feature = "log")] +use log::debug; + #[derive(Debug, PartialEq, serde::Deserialize)] /// These are the states an issue can be in. pub enum State { @@ -74,7 +77,8 @@ impl Issue { dependencies = Some(deps); } } else { - println!("ignoring unknown file in issue directory: {:?}", file_name); + #[cfg(feature = "log")] + debug!("ignoring unknown file in issue directory: {:?}", file_name); } } } diff --git a/src/issues.rs b/src/issues.rs index 9422ded..b45026f 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "log")] +use log::debug; + // Just a placeholder for now, get rid of this if we don't need it. #[derive(Debug, PartialEq, serde::Deserialize)] pub struct Config {} @@ -56,7 +59,8 @@ impl Issues { } else if direntry.file_name() == "config.toml" { issues.parse_config(direntry.path().as_path())?; } else { - println!( + #[cfg(feature = "log")] + debug!( "ignoring unknown file in issues directory: {:?}", direntry.file_name() ); From af024570413e72a70330a96b30893a904174a6e0 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 12:34:35 -0600 Subject: [PATCH 034/489] update 'description' in issue a26da230276d317e85f9fcca41c19d2e --- a26da230276d317e85f9fcca41c19d2e/description | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 a26da230276d317e85f9fcca41c19d2e/description diff --git a/a26da230276d317e85f9fcca41c19d2e/description b/a26da230276d317e85f9fcca41c19d2e/description new file mode 100644 index 0000000..9d2f2b3 --- /dev/null +++ b/a26da230276d317e85f9fcca41c19d2e/description @@ -0,0 +1,17 @@ +`ent edit ${ISSUE}` with no change fails + +After the user closes the editor, ent blindly tries to commit, which +fails like this: +``` +stdout: On branch entomologist-data +nothing to commit, working tree clean + +stderr: +Error: Failed to run git + +Caused by: + Oops, something went wrong +``` + +Probably we should run `git status` after the editor finishes, to see +if there's anything to commit. From 15f0ce57d588206cca14cc65c8b54ae4992220ae Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 7 Jul 2025 12:40:03 -0600 Subject: [PATCH 035/489] install.sh: use /usr/local/bin not /usr/bin --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 9a459a3..ba8faf0 100755 --- a/install.sh +++ b/install.sh @@ -2,7 +2,7 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) BINFILE="${SCRIPT_DIR}/target/release/ent" -INSTALL_DIR="/usr/bin" +INSTALL_DIR="/usr/local/bin" cargo build --release echo "copying ent to ${INSTALL_DIR}" From be6e168bb3bdd68678d999b37b2abb0dc5f5e138 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 12:42:47 -0600 Subject: [PATCH 036/489] update 'description' in issue 317ea8ccac1d414cde55771321bdec3 --- 317ea8ccac1d414cde55771321bdec3/description | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 317ea8ccac1d414cde55771321bdec3/description diff --git a/317ea8ccac1d414cde55771321bdec3/description b/317ea8ccac1d414cde55771321bdec3/description new file mode 100644 index 0000000..1ed3eb7 --- /dev/null +++ b/317ea8ccac1d414cde55771321bdec3/description @@ -0,0 +1,15 @@ +allow multiple read-only ent processes simultaneously + +Currently every time `ent` checks out a worktree of its "issues database", +it uses the local branch `entomologist-data`. This means only one +worktree can exist at a time, because each branch can only be checked +out into at most one worktree. + +Some ent operations are read-only which means we could `git worktree add` +the `origin/entomologist-data` branch instead, which uses a detached HEAD +checkout, which is sufficient for read-only operations like `ent list` +and `ent show`. + +This would be useful if you're sitting in `ent edit ${ISSUE_1}` and want +to look at another issue with `ent show ${ISSUE_2}` or list the issues +with `ent list`. From 172055c48048289ff8b25ca8f439864b351342a9 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 12:53:05 -0600 Subject: [PATCH 037/489] always render issue UUIDs as 128 bit hex numbers --- src/git.rs | 4 ++-- src/issue.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/git.rs b/src/git.rs index bb17763..caa5e4b 100644 --- a/src/git.rs +++ b/src/git.rs @@ -213,7 +213,7 @@ mod tests { fn test_create_orphan_branch() { let rnd: u128 = rand::random(); let mut branch = std::string::String::from("entomologist-test-branch-"); - branch.push_str(&format!("{:0x}", rnd)); + branch.push_str(&format!("{:032x}", rnd)); create_orphan_branch(&branch).unwrap(); git_remove_branch(&branch).unwrap(); } @@ -228,7 +228,7 @@ mod tests { fn test_branch_exists_1() { let rnd: u128 = rand::random(); let mut branch = std::string::String::from("entomologist-missing-branch-"); - branch.push_str(&format!("{:0x}", rnd)); + branch.push_str(&format!("{:032x}", rnd)); let r = git_branch_exists(&branch).unwrap(); assert_eq!(r, false); } diff --git a/src/issue.rs b/src/issue.rs index 8031300..bb8594a 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -107,7 +107,7 @@ impl Issue { pub fn new(dir: &std::path::Path) -> Result { let mut issue_dir = std::path::PathBuf::from(dir); let rnd: u128 = rand::random(); - issue_dir.push(&format!("{:0x}", rnd)); + issue_dir.push(&format!("{:032x}", rnd)); std::fs::create_dir(&issue_dir)?; Ok(Self { description: String::from(""), // FIXME: kind of bogus to use the empty string as None From ed1b4488b2ef09cf10181bb76fbbca95e399818c Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 7 Jul 2025 12:56:59 -0600 Subject: [PATCH 038/489] issue.rs: add state getter/setter --- src/issue.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/issue.rs b/src/issue.rs index 8031300..d3ecf3d 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -4,7 +4,7 @@ use std::str::FromStr; #[cfg(feature = "log")] use log::debug; -#[derive(Debug, PartialEq, serde::Deserialize)] +#[derive(Clone, Debug, PartialEq, serde::Deserialize)] /// These are the states an issue can be in. pub enum State { New, @@ -157,6 +157,23 @@ impl Issue { None => self.description.as_str(), } } + + pub fn set_state(&mut self, new_state: State) -> Result<(), IssueError> { + let mut state_filename = std::path::PathBuf::from(&self.dir); + state_filename.push("state"); + let mut state_file = std::fs::File::create(&state_filename)?; + write!(state_file, "{}", new_state)?; + crate::git::git_commit_file(&state_filename)?; + Ok(()) + } + + pub fn read_state(&mut self) -> Result<(), IssueError> { + let mut state_filename = std::path::PathBuf::from(&self.dir); + state_filename.push("state"); + let state_string = std::fs::read_to_string(state_filename)?; + self.state = State::from_str(state_string.trim())?; + Ok(()) + } } #[cfg(test)] From a6d2f7d1e83a0f86fd52324b73417925c27d8fc1 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 7 Jul 2025 13:07:55 -0600 Subject: [PATCH 039/489] issue.rs: add fmt::Display for State --- src/issue.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/issue.rs b/src/issue.rs index d3ecf3d..61f7072 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -1,3 +1,4 @@ +use core::fmt; use std::io::Write; use std::str::FromStr; @@ -62,6 +63,21 @@ impl FromStr for State { } } +impl fmt::Display for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let fmt_str = match self { + State::New => "new", + State::Backlog => "backlog", + State::Blocked => "blocked", + State::InProgress => "inprogress", + State::Done => "done", + State::WontDo => "wontdo", + + }; + write!(f, "{fmt_str}") + } +} + impl Issue { pub fn new_from_dir(dir: &std::path::Path) -> Result { let mut description: Option = None; From 0f46eb78172a75a8fca80f119fbe912293f9a1d1 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 7 Jul 2025 13:28:26 -0600 Subject: [PATCH 040/489] add State command to CLI --- src/bin/ent/main.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index d26dc3c..6a3c9f0 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -1,5 +1,6 @@ use clap::Parser; +use entomologist::issue::State; #[cfg(feature = "log")] use simple_logger; @@ -32,6 +33,9 @@ enum Commands { /// Show the full description of an issue. Show { issue_id: String }, + + /// Modify the state of an issue + State { issue_id: String, new_state: State }, } fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { @@ -82,6 +86,20 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } } } + Commands::State { issue_id, new_state } => { + let mut issues = + entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; + match issues.issues.get_mut(issue_id) { + Some(issue) => { + let old_state = issue.state.clone(); + issue.set_state(new_state.clone())?; + println!("issue {}: state {} -> {}", issue_id, old_state, new_state); + } + None => { + println!("issue {} not found", issue_id); + } + } + } } Ok(()) From 7b36befc73f06e60e1baeb7959e2ec3f85136e08 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 7 Jul 2025 13:27:48 -0600 Subject: [PATCH 041/489] update 'state' in issue da435e5e298b28dc223f9dcfe62a914 --- da435e5e298b28dc223f9dcfe62a914/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 da435e5e298b28dc223f9dcfe62a914/state diff --git a/da435e5e298b28dc223f9dcfe62a914/state b/da435e5e298b28dc223f9dcfe62a914/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/da435e5e298b28dc223f9dcfe62a914/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From fc3a1d903eb9118262ccbacb4a973442aee9c0f9 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 16:46:57 -0600 Subject: [PATCH 042/489] update 'description' in issue e089400e8a9e11fe9bf10d50b2f889d7 --- e089400e8a9e11fe9bf10d50b2f889d7/description | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e089400e8a9e11fe9bf10d50b2f889d7/description b/e089400e8a9e11fe9bf10d50b2f889d7/description index 1b0558e..6a4ff9e 100644 --- a/e089400e8a9e11fe9bf10d50b2f889d7/description +++ b/e089400e8a9e11fe9bf10d50b2f889d7/description @@ -1,4 +1,4 @@ -keep local `entomologist-data` branch in sync with `origin/entomologist-data` +keep local `entomologist-data` branch in sync with remote My git workflow does not use `git pull`, instead i: 1. `git fetch` From 475cfd83ce1a1319b963ab913dba11c9c7d1342c Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 7 Jul 2025 16:49:02 -0600 Subject: [PATCH 043/489] update 'state' in issue 198a7d56a19f0579fbc04f2ee9cc234f --- 198a7d56a19f0579fbc04f2ee9cc234f/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 198a7d56a19f0579fbc04f2ee9cc234f/state diff --git a/198a7d56a19f0579fbc04f2ee9cc234f/state b/198a7d56a19f0579fbc04f2ee9cc234f/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/198a7d56a19f0579fbc04f2ee9cc234f/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 6dda9d67e7c0e0ba5b52f73d4d3bff4795d7eb2a Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 7 Jul 2025 16:49:13 -0600 Subject: [PATCH 044/489] update 'state' in issue 198a7d56a19f0579fbc04f2ee9cc234f --- 198a7d56a19f0579fbc04f2ee9cc234f/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/198a7d56a19f0579fbc04f2ee9cc234f/state b/198a7d56a19f0579fbc04f2ee9cc234f/state index 505c028..348ebd9 100644 --- a/198a7d56a19f0579fbc04f2ee9cc234f/state +++ b/198a7d56a19f0579fbc04f2ee9cc234f/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From bcc8ba4f2147879c95f73913fb69a74ca1141412 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 7 Jul 2025 16:49:25 -0600 Subject: [PATCH 045/489] update CLI to have optional state control --- src/bin/ent/main.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 6a3c9f0..187d067 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -35,7 +35,7 @@ enum Commands { Show { issue_id: String }, /// Modify the state of an issue - State { issue_id: String, new_state: State }, + State { issue_id: String, new_state: Option }, } fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { @@ -91,9 +91,20 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; match issues.issues.get_mut(issue_id) { Some(issue) => { - let old_state = issue.state.clone(); - issue.set_state(new_state.clone())?; - println!("issue {}: state {} -> {}", issue_id, old_state, new_state); + let current_state = issue.state.clone(); + + match new_state { + Some(s) => { + issue.set_state(s.clone())?; + println!("issue: {}", issue_id); + println!("state: {} -> {}", current_state, s); + } + None => { + println!("issue: {}", issue_id); + println!("state: {}", current_state); + } + } + } None => { println!("issue {} not found", issue_id); From a5a523e6e2fc0316ff51149d1c141e91846ad8d1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 18:07:49 -0600 Subject: [PATCH 046/489] update 'description' in issue 397a3034f4330129d10d4379e94e7279 --- .../comments/397a3034f4330129d10d4379e94e7279/description | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 75cefad80aacbf23fc7b9c24a75aa236/comments/397a3034f4330129d10d4379e94e7279/description diff --git a/75cefad80aacbf23fc7b9c24a75aa236/comments/397a3034f4330129d10d4379e94e7279/description b/75cefad80aacbf23fc7b9c24a75aa236/comments/397a3034f4330129d10d4379e94e7279/description new file mode 100644 index 0000000..ff7c1e9 --- /dev/null +++ b/75cefad80aacbf23fc7b9c24a75aa236/comments/397a3034f4330129d10d4379e94e7279/description @@ -0,0 +1,5 @@ +Mostly implemented: +- each issue has a `comments/` directory +- each comment is a subdirectory of `comments/` +- each comment-directory is named with a 128-bit nonce +- each comment has only a single "field": `description` From b789a3d293cb73bdc293c3e3422b5b4ae48fead2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 15:30:46 -0600 Subject: [PATCH 047/489] ent show: show dependencies, if any --- src/bin/ent/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 187d067..b2eaee2 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -77,7 +77,10 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( match issues.issues.get(issue_id) { Some(issue) => { println!("issue {}", issue_id); - println!("state {:?}", issue.state); + println!("state: {:?}", issue.state); + if let Some(dependencies) = &issue.dependencies { + println!("dependencies: {:?}", dependencies); + } println!(""); println!("{}", issue.description); } From 4307ab98a0bede7ed11ba1da176d6fad53b7ceb5 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 16:14:19 -0600 Subject: [PATCH 048/489] better interface to looking up issue --- src/bin/ent/main.rs | 4 ++-- src/issues.rs | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index b2eaee2..83ebb7f 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -62,7 +62,7 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( Commands::Edit { issue_id } => { let mut issues = entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; - match issues.issues.get_mut(issue_id) { + match issues.get_mut_issue(issue_id) { Some(issue) => { issue.edit_description()?; } @@ -74,7 +74,7 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( Commands::Show { issue_id } => { let issues = entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; - match issues.issues.get(issue_id) { + match issues.get_issue(issue_id) { Some(issue) => { println!("issue {}", issue_id); println!("state: {:?}", issue.state); diff --git a/src/issues.rs b/src/issues.rs index a900ed6..2e40930 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -35,6 +35,14 @@ impl Issues { self.issues.insert(uuid, issue); } + pub fn get_issue(&self, issue_id: &str) -> Option<&crate::issue::Issue> { + self.issues.get(issue_id) + } + + pub fn get_mut_issue(&mut self, issue_id: &str) -> Option<&mut crate::issue::Issue> { + self.issues.get_mut(issue_id) + } + fn parse_config(&mut self, config_path: &std::path::Path) -> Result<(), ReadIssuesError> { let config_contents = std::fs::read_to_string(config_path)?; let config: Config = toml::from_str(&config_contents)?; From 035c150f4cc9aa271c2cf2a4b380970bfa8454e0 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 16:14:42 -0600 Subject: [PATCH 049/489] ent: better error reporting --- src/bin/ent/main.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 83ebb7f..9c31c60 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -35,7 +35,10 @@ enum Commands { Show { issue_id: String }, /// Modify the state of an issue - State { issue_id: String, new_state: Option }, + State { + issue_id: String, + new_state: Option, + }, } fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { @@ -47,6 +50,7 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( println!("{} {} ({:?})", uuid, issue.title(), issue.state); } } + Commands::New { description: Some(description), } => { @@ -54,11 +58,13 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( issue.set_description(description)?; println!("created new issue '{}'", issue.title()); } + Commands::New { description: None } => { let mut issue = entomologist::issue::Issue::new(issues_dir)?; issue.edit_description()?; println!("created new issue '{}'", issue.title()); } + Commands::Edit { issue_id } => { let mut issues = entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; @@ -67,10 +73,11 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( issue.edit_description()?; } None => { - println!("issue {} not found", issue_id); + return Err(anyhow::anyhow!("issue {} not found", issue_id)); } } } + Commands::Show { issue_id } => { let issues = entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; @@ -85,17 +92,20 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( println!("{}", issue.description); } None => { - println!("issue {} not found", issue_id); + return Err(anyhow::anyhow!("issue {} not found", issue_id)); } } } - Commands::State { issue_id, new_state } => { + + Commands::State { + issue_id, + new_state, + } => { let mut issues = entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; match issues.issues.get_mut(issue_id) { Some(issue) => { - let current_state = issue.state.clone(); - + let current_state = issue.state.clone(); match new_state { Some(s) => { issue.set_state(s.clone())?; @@ -107,10 +117,9 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( println!("state: {}", current_state); } } - } None => { - println!("issue {} not found", issue_id); + return Err(anyhow::anyhow!("issue {} not found", issue_id)); } } } From 0529d0132a2754cf6c0d059764c3e75198c0deff Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 7 Jul 2025 19:46:09 -0600 Subject: [PATCH 050/489] update 'state' in issue da435e5e298b28dc223f9dcfe62a914 --- da435e5e298b28dc223f9dcfe62a914/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/da435e5e298b28dc223f9dcfe62a914/state b/da435e5e298b28dc223f9dcfe62a914/state index 505c028..348ebd9 100644 --- a/da435e5e298b28dc223f9dcfe62a914/state +++ b/da435e5e298b28dc223f9dcfe62a914/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 50509dcf59219b4bfa7f5633e3f44308bb78e838 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 15:26:26 -0600 Subject: [PATCH 051/489] basic comment support in lib --- src/comment.rs | 104 ++++++++++++++++++ src/issue.rs | 42 +++++++ src/issues.rs | 20 ++++ src/lib.rs | 1 + .../description | 3 + 5 files changed, 170 insertions(+) create mode 100644 src/comment.rs create mode 100644 test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/comments/9055dac36045fe36545bed7ae7b49347/description diff --git a/src/comment.rs b/src/comment.rs new file mode 100644 index 0000000..ab2ced9 --- /dev/null +++ b/src/comment.rs @@ -0,0 +1,104 @@ +use std::io::Write; + +#[derive(Debug, PartialEq)] +pub struct Comment { + pub description: String, + + /// This is the directory that the comment lives in. Only used + /// internally by the entomologist library. + pub dir: std::path::PathBuf, +} + +#[derive(Debug, thiserror::Error)] +pub enum CommentError { + #[error(transparent)] + StdIoError(#[from] std::io::Error), + #[error("Failed to parse comment")] + CommentParseError, + #[error("Failed to run git")] + GitError(#[from] crate::git::GitError), + #[error("Failed to run editor")] + EditorError, +} + +impl Comment { + pub fn new_from_dir(comment_dir: &std::path::Path) -> Result { + let mut description: Option = None; + + for direntry in comment_dir.read_dir()? { + if let Ok(direntry) = direntry { + let file_name = direntry.file_name(); + if file_name == "description" { + description = Some(std::fs::read_to_string(direntry.path())?); + } else { + #[cfg(feature = "log")] + debug!( + "ignoring unknown file in comment directory: {:?}", + file_name + ); + } + } + } + + if description == None { + return Err(CommentError::CommentParseError); + } + + Ok(Self { + description: description.unwrap(), + dir: std::path::PathBuf::from(comment_dir), + }) + } + + pub fn set_description(&mut self, description: &str) -> Result<(), CommentError> { + self.description = String::from(description); + let mut description_filename = std::path::PathBuf::from(&self.dir); + description_filename.push("description"); + let mut description_file = std::fs::File::create(&description_filename)?; + write!(description_file, "{}", description)?; + crate::git::git_commit_file(&description_filename)?; + Ok(()) + } + + pub fn read_description(&mut self) -> Result<(), CommentError> { + let mut description_filename = std::path::PathBuf::from(&self.dir); + description_filename.push("description"); + self.description = std::fs::read_to_string(description_filename)?; + Ok(()) + } + + pub fn edit_description(&mut self) -> Result<(), CommentError> { + let mut description_filename = std::path::PathBuf::from(&self.dir); + description_filename.push("description"); + let result = std::process::Command::new("vi") + .arg(&description_filename.as_mut_os_str()) + .spawn()? + .wait_with_output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(CommentError::EditorError); + } + crate::git::git_commit_file(&description_filename)?; + self.read_description()?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn read_comment_0() { + let comment_dir = + std::path::Path::new("test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/comments/9055dac36045fe36545bed7ae7b49347"); + let comment = Comment::new_from_dir(comment_dir).unwrap(); + let expected = Comment { + description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561\n\nIt has multiple lines\n"), + + dir: std::path::PathBuf::from(comment_dir), + }; + assert_eq!(comment, expected); + } +} diff --git a/src/issue.rs b/src/issue.rs index f931be3..c762c82 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -23,6 +23,7 @@ pub struct Issue { pub description: String, pub state: State, pub dependencies: Option>, + pub comments: std::collections::HashMap, /// This is the directory that the issue lives in. Only used /// internally by the entomologist library. @@ -33,6 +34,8 @@ pub struct Issue { pub enum IssueError { #[error(transparent)] StdIoError(#[from] std::io::Error), + #[error(transparent)] + CommentError(#[from] crate::comment::CommentError), #[error("Failed to parse issue")] IssueParseError, #[error("Failed to run git")] @@ -83,6 +86,7 @@ impl Issue { let mut description: Option = None; let mut state = State::New; // default state, if not specified in the issue let mut dependencies: Option> = None; + let mut comments = std::collections::HashMap::::new(); for direntry in dir.read_dir()? { if let Ok(direntry) = direntry { @@ -101,6 +105,8 @@ impl Issue { if deps.len() > 0 { dependencies = Some(deps); } + } else if file_name == "comments" && direntry.metadata()?.is_dir() { + Self::read_comments(&mut comments, &direntry.path())?; } else { #[cfg(feature = "log")] debug!("ignoring unknown file in issue directory: {:?}", file_name); @@ -116,10 +122,43 @@ impl Issue { description: description.unwrap(), state: state, dependencies, + comments, dir: std::path::PathBuf::from(dir), }) } + fn read_comments( + comments: &mut std::collections::HashMap, + dir: &std::path::Path, + ) -> Result<(), IssueError> { + for direntry in dir.read_dir()? { + if let Ok(direntry) = direntry { + let uuid = direntry.file_name(); + let comment = crate::comment::Comment::new_from_dir(&direntry.path())?; + comments.insert(String::from(uuid.to_string_lossy()), comment); + } + } + Ok(()) + } + + pub fn new_comment(&mut self) -> Result { + let mut dir = std::path::PathBuf::from(&self.dir); + dir.push("comments"); + if !dir.exists() { + println!("creating {}", dir.to_string_lossy()); + std::fs::create_dir(&dir)?; + } + + let rnd: u128 = rand::random(); + dir.push(&format!("{:032x}", rnd)); + std::fs::create_dir(&dir)?; + + Ok(crate::comment::Comment { + description: String::from(""), // FIXME + dir, + }) + } + pub fn new(dir: &std::path::Path) -> Result { let mut issue_dir = std::path::PathBuf::from(dir); let rnd: u128 = rand::random(); @@ -129,6 +168,7 @@ impl Issue { description: String::from(""), // FIXME: kind of bogus to use the empty string as None state: State::New, dependencies: None, + comments: std::collections::HashMap::::new(), dir: issue_dir, }) } @@ -204,6 +244,7 @@ mod tests { description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), state: State::New, dependencies: None, + comments: std::collections::HashMap::::new(), dir: std::path::PathBuf::from(issue_dir), }; assert_eq!(issue, expected); @@ -217,6 +258,7 @@ mod tests { description: String::from("minimal"), state: State::InProgress, dependencies: None, + comments: std::collections::HashMap::::new(), dir: std::path::PathBuf::from(issue_dir), }; assert_eq!(issue, expected); diff --git a/src/issues.rs b/src/issues.rs index 2e40930..28df2e9 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -99,6 +99,7 @@ mod tests { description: String::from("minimal"), state: crate::issue::State::InProgress, dependencies: None, + comments: std::collections::HashMap::::new(), dir, }, ); @@ -112,6 +113,7 @@ mod tests { description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), state: crate::issue::State::New, dependencies: None, + comments: std::collections::HashMap::::new(), dir, } ); @@ -134,6 +136,7 @@ mod tests { description: String::from("oh yeah we got titles"), state: crate::issue::State::Done, dependencies: None, + comments: std::collections::HashMap::::new(), dir, }, ); @@ -141,12 +144,26 @@ mod tests { let uuid = String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"); let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); + let mut comment_dir = dir.clone(); + let comment_uuid = String::from("9055dac36045fe36545bed7ae7b49347"); + comment_dir.push("comments"); + comment_dir.push(&comment_uuid); + let mut expected_comments = + std::collections::HashMap::::new(); + expected_comments.insert( + String::from(&comment_uuid), + crate::comment::Comment { + description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561\n\nIt has multiple lines\n"), + dir: std::path::PathBuf::from(comment_dir), + } + ); expected.add_issue( uuid, crate::issue::Issue { description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), state: crate::issue::State::WontDo, dependencies: None, + comments: expected_comments, dir, }, ); @@ -169,6 +186,7 @@ mod tests { description: String::from("oh yeah we got titles\n"), state: crate::issue::State::Done, dependencies: None, + comments: std::collections::HashMap::::new(), dir, }, ); @@ -182,6 +200,7 @@ mod tests { description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), state: crate::issue::State::WontDo, dependencies: None, + comments: std::collections::HashMap::::new(), dir, }, ); @@ -198,6 +217,7 @@ mod tests { crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), crate::issue::IssueHandle::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), ]), + comments: std::collections::HashMap::::new(), dir, }, ); diff --git a/src/lib.rs b/src/lib.rs index cca1c90..e129eff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod comment; pub mod git; pub mod issue; pub mod issues; diff --git a/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/comments/9055dac36045fe36545bed7ae7b49347/description b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/comments/9055dac36045fe36545bed7ae7b49347/description new file mode 100644 index 0000000..f9de678 --- /dev/null +++ b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/comments/9055dac36045fe36545bed7ae7b49347/description @@ -0,0 +1,3 @@ +This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561 + +It has multiple lines From 9870d42fdcbad2e62de575dc8e403ba5f4889580 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 15:33:43 -0600 Subject: [PATCH 052/489] ent show: include comments --- src/bin/ent/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 9c31c60..4a86883 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -90,6 +90,11 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } println!(""); println!("{}", issue.description); + for (uuid, comment) in issue.comments.iter() { + println!(""); + println!("comment: {}", uuid); + println!("{}", comment.description); + } } None => { return Err(anyhow::anyhow!("issue {} not found", issue_id)); From 8ac4ca4c543ccddc15aef154601f753df5f51c42 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 16:15:01 -0600 Subject: [PATCH 053/489] add `ent comment`, to add a comment on an issue --- src/bin/ent/main.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 4a86883..63fba60 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -39,6 +39,12 @@ enum Commands { issue_id: String, new_state: Option, }, + + /// Create a new comment on an issue. + Comment { + issue_id: String, + description: Option, + }, } fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { @@ -128,6 +134,27 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } } } + + Commands::Comment { + issue_id, + description, + } => { + let mut issues = + entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; + let Some(issue) = issues.get_mut_issue(issue_id) else { + return Err(anyhow::anyhow!("issue {} not found", issue_id)); + }; + println!("found issue {}", issue.title()); + let mut comment = issue.new_comment()?; + match description { + Some(description) => { + comment.set_description(description)?; + } + None => { + comment.edit_description()?; + } + } + } } Ok(()) From 99fe1e6d4bb3f02fc72487540212c704f09e46b4 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 21:18:49 -0600 Subject: [PATCH 054/489] update 'description' in issue 75cefad80aacbf23fc7b9c24a75aa236 --- 75cefad80aacbf23fc7b9c24a75aa236/description | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/75cefad80aacbf23fc7b9c24a75aa236/description b/75cefad80aacbf23fc7b9c24a75aa236/description index 212fa34..6bb20e9 100644 --- a/75cefad80aacbf23fc7b9c24a75aa236/description +++ b/75cefad80aacbf23fc7b9c24a75aa236/description @@ -1,6 +1,6 @@ -# implement `ent comment ${ISSUE} [-m ${MESSAGE}]` +# implement `ent comment ISSUE [DESCRIPTION]` - each issue dir has a `comments` subdir -- each comment is identified by a sha1-style uid -- each comment is a file or directory under the `${ISSUE}/comments` -- comments are ordered by ctime? +- each comment is identified by a 128-bit nonce +- each comment is a directory under `${ISSUE}/comments` +- each comment has a single "field": `description` From 4c9b401153f47cd659cba40a94eb4964afea5ef2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 21:19:35 -0600 Subject: [PATCH 055/489] update 'state' in issue 75cefad80aacbf23fc7b9c24a75aa236 --- 75cefad80aacbf23fc7b9c24a75aa236/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 75cefad80aacbf23fc7b9c24a75aa236/state diff --git a/75cefad80aacbf23fc7b9c24a75aa236/state b/75cefad80aacbf23fc7b9c24a75aa236/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/75cefad80aacbf23fc7b9c24a75aa236/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 08486ac158ed678e3f363a82be91c9ce3e362921 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 21:24:25 -0600 Subject: [PATCH 056/489] manually rename some issue dirs to be 128 bit --- .../description | 0 .../description | 0 .../state | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {317ea8ccac1d414cde55771321bdec3 => 317ea8ccac1d414cde55771321bdec30}/description (100%) rename {da435e5e298b28dc223f9dcfe62a914 => da435e5e298b28dc223f9dcfe62a9140}/description (100%) rename {da435e5e298b28dc223f9dcfe62a914 => da435e5e298b28dc223f9dcfe62a9140}/state (100%) diff --git a/317ea8ccac1d414cde55771321bdec3/description b/317ea8ccac1d414cde55771321bdec30/description similarity index 100% rename from 317ea8ccac1d414cde55771321bdec3/description rename to 317ea8ccac1d414cde55771321bdec30/description diff --git a/da435e5e298b28dc223f9dcfe62a914/description b/da435e5e298b28dc223f9dcfe62a9140/description similarity index 100% rename from da435e5e298b28dc223f9dcfe62a914/description rename to da435e5e298b28dc223f9dcfe62a9140/description diff --git a/da435e5e298b28dc223f9dcfe62a914/state b/da435e5e298b28dc223f9dcfe62a9140/state similarity index 100% rename from da435e5e298b28dc223f9dcfe62a914/state rename to da435e5e298b28dc223f9dcfe62a9140/state From 98f824e5ead01b8dac68a64c49ee4b0dc8cc89ee Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 7 Jul 2025 21:30:31 -0600 Subject: [PATCH 057/489] update 'description' in issue ee87637887476815b189a0d19b9d9a4b --- .../comments/ee87637887476815b189a0d19b9d9a4b/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 75cefad80aacbf23fc7b9c24a75aa236/comments/ee87637887476815b189a0d19b9d9a4b/description diff --git a/75cefad80aacbf23fc7b9c24a75aa236/comments/ee87637887476815b189a0d19b9d9a4b/description b/75cefad80aacbf23fc7b9c24a75aa236/comments/ee87637887476815b189a0d19b9d9a4b/description new file mode 100644 index 0000000..6229ffd --- /dev/null +++ b/75cefad80aacbf23fc7b9c24a75aa236/comments/ee87637887476815b189a0d19b9d9a4b/description @@ -0,0 +1 @@ +should we also add timestamps to the comments in text? or should we just rely on the filesystem/git time? \ No newline at end of file From 87bbed54275bd77855aa0764e9c66439634c694f Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 22:29:25 -0600 Subject: [PATCH 058/489] update 'state' in issue 8c73c9fd5bc4f551ee5069035ae6e866 --- 8c73c9fd5bc4f551ee5069035ae6e866/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 8c73c9fd5bc4f551ee5069035ae6e866/state diff --git a/8c73c9fd5bc4f551ee5069035ae6e866/state b/8c73c9fd5bc4f551ee5069035ae6e866/state new file mode 100644 index 0000000..348ebd9 --- /dev/null +++ b/8c73c9fd5bc4f551ee5069035ae6e866/state @@ -0,0 +1 @@ +done \ No newline at end of file From dedfa127771b913f3169d26b6df6d6640a2bdbbf Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 22:33:08 -0600 Subject: [PATCH 059/489] update 'description' in issue 3fb80cb5ffed0d536568c8470bbe011a --- .../comments/3fb80cb5ffed0d536568c8470bbe011a/description | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 b738f2842db428df1b4aad0192a7f36c/comments/3fb80cb5ffed0d536568c8470bbe011a/description diff --git a/b738f2842db428df1b4aad0192a7f36c/comments/3fb80cb5ffed0d536568c8470bbe011a/description b/b738f2842db428df1b4aad0192a7f36c/comments/3fb80cb5ffed0d536568c8470bbe011a/description new file mode 100644 index 0000000..26124b8 --- /dev/null +++ b/b738f2842db428df1b4aad0192a7f36c/comments/3fb80cb5ffed0d536568c8470bbe011a/description @@ -0,0 +1,2 @@ +I've used asciidoc to write docs, it's got a tolerable syntax and +generates usable manpages, html, and pdf. From cd4eb8206777335cfabbb771430a497e6dfb5f0d Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 22:33:29 -0600 Subject: [PATCH 060/489] remove a useless debug message --- src/bin/ent/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 63fba60..1c9ddaa 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -144,7 +144,6 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( let Some(issue) = issues.get_mut_issue(issue_id) else { return Err(anyhow::anyhow!("issue {} not found", issue_id)); }; - println!("found issue {}", issue.title()); let mut comment = issue.new_comment()?; match description { Some(description) => { From 8ae970c92ed71789ac746b3ae43251cfdd3498bb Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 22:47:18 -0600 Subject: [PATCH 061/489] update 'description' in issue acb06210852087afceaf4d9e015e3c6b --- .../comments/acb06210852087afceaf4d9e015e3c6b/description | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 75cefad80aacbf23fc7b9c24a75aa236/comments/acb06210852087afceaf4d9e015e3c6b/description diff --git a/75cefad80aacbf23fc7b9c24a75aa236/comments/acb06210852087afceaf4d9e015e3c6b/description b/75cefad80aacbf23fc7b9c24a75aa236/comments/acb06210852087afceaf4d9e015e3c6b/description new file mode 100644 index 0000000..8c82c0f --- /dev/null +++ b/75cefad80aacbf23fc7b9c24a75aa236/comments/acb06210852087afceaf4d9e015e3c6b/description @@ -0,0 +1,5 @@ +> should we also add timestamps to the comments in text? or should we just rely on the filesystem/git time? + +I think the "creation time" of a comment is the commit time of the oldest +commit that touches the comment. We can get this from something like: +$ git log --pretty=format:%at entomologist-data -- ${ISSUE}/comments/${COMMENT} From 7d9284bf91141f8e834f03ed60f6d577ca32305f Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 22:27:24 -0600 Subject: [PATCH 062/489] `ent list` now accepts a filter, default "state=New,Backlog,Blocked,InProgress" --- src/bin/ent/main.rs | 13 ++++++++++--- src/issue.rs | 7 ++++--- src/lib.rs | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 1c9ddaa..1982feb 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -23,7 +23,11 @@ struct Args { #[derive(clap::Subcommand, Debug)] enum Commands { /// List issues. - List, + List { + /// Filter string, describes issues to include in the list. + #[arg(default_value_t = String::from("state=New,Backlog,Blocked,InProgress"))] + filter: String, + }, /// Create a new issue. New { description: Option }, @@ -49,11 +53,14 @@ enum Commands { fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { match &args.command { - Commands::List => { + Commands::List { filter } => { let issues = entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; + let filter = entomologist::parse_filter(filter)?; for (uuid, issue) in issues.issues.iter() { - println!("{} {} ({:?})", uuid, issue.title(), issue.state); + if filter.include_states.contains(&issue.state) { + println!("{} {} ({:?})", uuid, issue.title(), issue.state); + } } } diff --git a/src/issue.rs b/src/issue.rs index c762c82..6ce8e46 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -5,7 +5,7 @@ use std::str::FromStr; #[cfg(feature = "log")] use log::debug; -#[derive(Clone, Debug, PartialEq, serde::Deserialize)] +#[derive(Clone, Debug, Eq, Hash, PartialEq, serde::Deserialize)] /// These are the states an issue can be in. pub enum State { New, @@ -38,6 +38,8 @@ pub enum IssueError { CommentError(#[from] crate::comment::CommentError), #[error("Failed to parse issue")] IssueParseError, + #[error("Failed to parse state")] + StateParseError, #[error("Failed to run git")] GitError(#[from] crate::git::GitError), #[error("Failed to run editor")] @@ -61,7 +63,7 @@ impl FromStr for State { } else if s == "wontdo" { Ok(State::WontDo) } else { - Err(IssueError::IssueParseError) + Err(IssueError::StateParseError) } } } @@ -75,7 +77,6 @@ impl fmt::Display for State { State::InProgress => "inprogress", State::Done => "done", State::WontDo => "wontdo", - }; write!(f, "{fmt_str}") } diff --git a/src/lib.rs b/src/lib.rs index e129eff..77a00d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,40 @@ +use std::str::FromStr; + pub mod comment; pub mod git; pub mod issue; pub mod issues; + +#[derive(Debug, thiserror::Error)] +pub enum ParseFilterError { + #[error("Failed to parse filter")] + ParseError, + #[error(transparent)] + IssueParseError(#[from] crate::issue::IssueError), +} + +// FIXME: It's easy to imagine a full dsl for filtering issues, for now +// i'm starting with obvious easy things. Chumsky looks appealing but +// more research is needed. +#[derive(Debug)] +pub struct Filter { + pub include_states: std::collections::HashSet, +} + +// Parses a filter description matching "state=STATE[,STATE*]" +pub fn parse_filter(filter_str: &str) -> Result { + let tokens: Vec<&str> = filter_str.split("=").collect(); + if tokens.len() != 2 { + return Err(ParseFilterError::ParseError); + } + if tokens[0] != "state" { + return Err(ParseFilterError::ParseError); + } + + let mut include_states = std::collections::HashSet::::new(); + for s in tokens[1].split(",") { + include_states.insert(crate::issue::State::from_str(s)?); + } + + Ok(Filter { include_states }) +} From fb712b8d7edda5149eb3ae3192bfb3016829a0f2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 00:23:53 -0600 Subject: [PATCH 063/489] update 'description' in issue a97c817024233be0e34536dfb1323070 --- a97c817024233be0e34536dfb1323070/description | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/a97c817024233be0e34536dfb1323070/description b/a97c817024233be0e34536dfb1323070/description index 4feea71..a20505b 100644 --- a/a97c817024233be0e34536dfb1323070/description +++ b/a97c817024233be0e34536dfb1323070/description @@ -1 +1 @@ -update ent new to output the issue ID \ No newline at end of file +update `ent new` to output the issue ID \ No newline at end of file From f03595c2a61f5044640e40a50216ff2c0bc70fa2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 00:25:42 -0600 Subject: [PATCH 064/489] update 'state' in issue 8edf884dbde5828a30a4dccad503f28a --- 8edf884dbde5828a30a4dccad503f28a/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 8edf884dbde5828a30a4dccad503f28a/state diff --git a/8edf884dbde5828a30a4dccad503f28a/state b/8edf884dbde5828a30a4dccad503f28a/state new file mode 100644 index 0000000..9e3b09d --- /dev/null +++ b/8edf884dbde5828a30a4dccad503f28a/state @@ -0,0 +1 @@ +wontdo \ No newline at end of file From 5f7bb06aa85e1ba05b615c09eaf64d72aed20d14 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 00:25:50 -0600 Subject: [PATCH 065/489] update 'state' in issue e089400e8a9e11fe9bf10d50b2f889d7 --- e089400e8a9e11fe9bf10d50b2f889d7/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 e089400e8a9e11fe9bf10d50b2f889d7/state diff --git a/e089400e8a9e11fe9bf10d50b2f889d7/state b/e089400e8a9e11fe9bf10d50b2f889d7/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/e089400e8a9e11fe9bf10d50b2f889d7/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 87e591e98c9f8b3196c14c1f0ac0379882b39a64 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 00:31:18 -0600 Subject: [PATCH 066/489] update 'description' in issue e089400e8a9e11fe9bf10d50b2f889d7 --- e089400e8a9e11fe9bf10d50b2f889d7/description | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/e089400e8a9e11fe9bf10d50b2f889d7/description b/e089400e8a9e11fe9bf10d50b2f889d7/description index 6a4ff9e..7b853a3 100644 --- a/e089400e8a9e11fe9bf10d50b2f889d7/description +++ b/e089400e8a9e11fe9bf10d50b2f889d7/description @@ -1,4 +1,4 @@ -keep local `entomologist-data` branch in sync with remote +add `ent sync` to keep local `entomologist-data` branch in sync with remote My git workflow does not use `git pull`, instead i: 1. `git fetch` @@ -8,8 +8,9 @@ When i run `git fetch` to fetch new issues, my local `entomologist-data` branch does not get automatically synchronized with `origin/entomologist-data`. -The clumsy workaround is: +My current clumsy manual workaround is: 1. `git fetch` 2. `git checkout entomologist-data` 3. `git merge origin/entomologist-data` 4. `git checkout ${BRANCH}` +5. `git push origin entomologist-data` From 24c58409c1ab2981fd60a586d333c84468d4bb31 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 00:37:15 -0600 Subject: [PATCH 067/489] update 'description' in issue fd81241f795333b64e7911cfb1b57c8f --- fd81241f795333b64e7911cfb1b57c8f/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 fd81241f795333b64e7911cfb1b57c8f/description diff --git a/fd81241f795333b64e7911cfb1b57c8f/description b/fd81241f795333b64e7911cfb1b57c8f/description new file mode 100644 index 0000000..fdb0c29 --- /dev/null +++ b/fd81241f795333b64e7911cfb1b57c8f/description @@ -0,0 +1 @@ +commit messages in the `entomologist-data` branch could be better From 381041f73e6dc91a694c7a551928dd5919f5b779 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 10:03:07 -0600 Subject: [PATCH 068/489] update 'description' in issue 093e87e8049b93bfa2d8fcd544cae75f --- 093e87e8049b93bfa2d8fcd544cae75f/description | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 093e87e8049b93bfa2d8fcd544cae75f/description diff --git a/093e87e8049b93bfa2d8fcd544cae75f/description b/093e87e8049b93bfa2d8fcd544cae75f/description new file mode 100644 index 0000000..b6ac248 --- /dev/null +++ b/093e87e8049b93bfa2d8fcd544cae75f/description @@ -0,0 +1,6 @@ +add optional 'assignee' to issue + +This is probably just another free-form text string for now, maybe +something fancier in the future. + +As part of this, teach `ent list FILTER` to filter by assignee. From 454d70b9b36ff1634144cbca0f44e3a8617e1a03 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 10:17:11 -0600 Subject: [PATCH 069/489] update 'description' in issue f57708cdac0607194ba61824e857b37c --- f57708cdac0607194ba61824e857b37c/description | 59 ++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 f57708cdac0607194ba61824e857b37c/description diff --git a/f57708cdac0607194ba61824e857b37c/description b/f57708cdac0607194ba61824e857b37c/description new file mode 100644 index 0000000..71cba23 --- /dev/null +++ b/f57708cdac0607194ba61824e857b37c/description @@ -0,0 +1,59 @@ +shorten uuids to uniqueness + +We currently use 128-bit uuids for all objects (Issue and Comment), +which is a bit cumbersome to work with on the command line. + +A typical issue list might look like this: +``` +$ ent list +093e87e8049b93bfa2d8fcd544cae75f add optional 'assignee' to issue (New) +198a7d56a19f0579fbc04f2ee9cc234f fix ignoring unknown file in issues directory: "README.md" (Done) +1ebdee0502937bf934bb0d72256dbdd1 add delete subcommand to delete ENTries (heh) (New) +1f85dfac686d5ea2417b2b07f7e1ff01 # implement `ent attach ${ISSUE} ${FILE}` (New) +317ea8ccac1d414cde55771321bdec30 allow multiple read-only ent processes simultaneously (New) +75cefad80aacbf23fc7b9c24a75aa236 # implement `ent comment ISSUE [DESCRIPTION]` (InProgress) +7da3bd5b72de0a05936b094db5d24304 implement `ent edit ${COMMENT}` (New) +8c73c9fd5bc4f551ee5069035ae6e866 migrate the Todo list into entomologist (Done) +8edf884dbde5828a30a4dccad503f28a add sync subcommand to sync entomologist-data branch (WontDo) +a26da230276d317e85f9fcca41c19d2e `ent edit ${ISSUE}` with no change fails (New) +a97c817024233be0e34536dfb1323070 update `ent new` to output the issue ID (New) +b738f2842db428df1b4aad0192a7f36c write a manpage (New) +da435e5e298b28dc223f9dcfe62a9140 add user control over state transitions (Done) +e089400e8a9e11fe9bf10d50b2f889d7 add `ent sync` to keep local `entomologist-data` branch in sync with remote (InProgress) +eee4a129dacac9ddff2e50580e822cbf ent edit fails to find file/directory (New) +f57708cdac0607194ba61824e857b37c shorten uuids to uniqueness (New) +fd81241f795333b64e7911cfb1b57c8f commit messages in the `entomologist-data` branch could be better (New) +``` + +After deserializing the `entomologist-data` branch into memory, examine +all uuids and determine how many nibbles we actually need to use to +ensure uniqueness. + +Per git convention, we'll use/keep the left-most, aka most significant +part of the uuid and ignore/discard the right-most/least-significant +part of the uuids. + +The issue list above would look like this: +``` +$ ent list +09 add optional 'assignee' to issue (New) +19 fix ignoring unknown file in issues directory: "README.md" (Done) +1e add delete subcommand to delete ENTries (heh) (New) +1f # implement `ent attach ${ISSUE} ${FILE}` (New) +31 allow multiple read-only ent processes simultaneously (New) +75 # implement `ent comment ISSUE [DESCRIPTION]` (InProgress) +7d implement `ent edit ${COMMENT}` (New) +8c migrate the Todo list into entomologist (Done) +8e add sync subcommand to sync entomologist-data branch (WontDo) +a2 `ent edit ${ISSUE}` with no change fails (New) +a9 update `ent new` to output the issue ID (New) +b7 write a manpage (New) +da add user control over state transitions (Done) +e0 add `ent sync` to keep local `entomologist-data` branch in sync with remote (InProgress) +ee ent edit fails to find file/directory (New) +f5 shorten uuids to uniqueness (New) +fd commit messages in the `entomologist-data` branch could be better (New) +``` + +And we could address issues by these shorter ids: +`$ ent edit f5` From ccabfa4ec8367a9e99d060cdd4979ca85a6823c6 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 10:49:10 -0600 Subject: [PATCH 070/489] remove an old debug log message from Issue::new_comment() --- src/issue.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/issue.rs b/src/issue.rs index c762c82..e099616 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -75,7 +75,6 @@ impl fmt::Display for State { State::InProgress => "inprogress", State::Done => "done", State::WontDo => "wontdo", - }; write!(f, "{fmt_str}") } @@ -145,7 +144,6 @@ impl Issue { let mut dir = std::path::PathBuf::from(&self.dir); dir.push("comments"); if !dir.exists() { - println!("creating {}", dir.to_string_lossy()); std::fs::create_dir(&dir)?; } From 9c54a92152f58d7868023eb2ffcadf9b9e0f4dcb Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 10:18:45 -0600 Subject: [PATCH 071/489] add `ent sync` In a worktree with the `entomologist-data` branch checked out in it: 1. `git fetch REMOTE` 2. `git merge REMOTE/BRANCH` 3. `git push REMOTE BRANCH` Pretty straight-forward. If anything goes wrong we error out and ask the human to help. --- src/bin/ent/main.rs | 27 ++++++++++++++++++++++ src/git.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 1c9ddaa..1363641 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -45,6 +45,15 @@ enum Commands { issue_id: String, description: Option, }, + + /// Sync entomologist data with remote. This fetches from the remote, + /// merges the remote entomologist data branch with the local one, + /// and pushes the result back to the remote. + Sync { + /// Name of the git remote to sync with. + #[arg(default_value_t = String::from("origin"))] + remote: String, + }, } fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { @@ -154,6 +163,24 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } } } + + Commands::Sync { remote } => { + if args.issues_dir.is_some() { + return Err(anyhow::anyhow!( + "`sync` operates on a branch, don't specify `issues_dir`" + )); + } + // FIXME: Kinda bogus to re-do this thing we just did in + // `main()`. Maybe `main()` shouldn't create the worktree, + // maybe we should do it here in `handle_command()`? + // That way also each command could decide if it wants a + // read-only worktree or a read/write one. + let branch = match &args.issues_branch { + Some(branch) => branch, + None => "entomologist-data", + }; + entomologist::git::sync(issues_dir, remote, branch)?; + } } Ok(()) diff --git a/src/git.rs b/src/git.rs index caa5e4b..30f703e 100644 --- a/src/git.rs +++ b/src/git.rs @@ -124,6 +124,62 @@ pub fn git_commit_file(file: &std::path::Path) -> Result<(), GitError> { Ok(()) } +pub fn git_fetch(dir: &std::path::Path, remote: &str) -> Result<(), GitError> { + let result = std::process::Command::new("git") + .args(["fetch", remote]) + .current_dir(dir) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + Ok(()) +} + +pub fn sync(dir: &std::path::Path, remote: &str, branch: &str) -> Result<(), GitError> { + // We do all the work in a directory that's (FIXME) hopefully a + // worktree. If anything goes wrong we just fail out and ask the + // human to fix it by hand :-/ + // 1. `git fetch` + // 2. `git merge REMOTE/BRANCH` + // 3. `git push REMOTE BRANCH` + + git_fetch(dir, remote)?; + + // Merge remote branch into local. + let result = std::process::Command::new("git") + .args(["merge", &format!("{}/{}", remote, branch)]) + .current_dir(dir) + .output()?; + if !result.status.success() { + println!( + "Sync failed! Merge error! Help, a human needs to fix the mess in {:?}", + dir + ); + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + + // Push merged branch to remote. + let result = std::process::Command::new("git") + .args(["push", remote, branch]) + .current_dir(dir) + .output()?; + if !result.status.success() { + println!( + "Sync failed! Push error! Help, a human needs to fix the mess in {:?}", + dir + ); + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + + Ok(()) +} + pub fn create_orphan_branch(branch: &str) -> Result<(), GitError> { { let tmp_worktree = tempfile::tempdir().unwrap(); From 2106c69271716c0c7a76e9a200b96b83764f31c8 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 11:10:03 -0600 Subject: [PATCH 072/489] `ent sync`: report success if it all worked --- src/bin/ent/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 1363641..10d0637 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -180,6 +180,7 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( None => "entomologist-data", }; entomologist::git::sync(issues_dir, remote, branch)?; + println!("synced {:?} with {:?}", branch, remote); } } From 399f246472590bb909f540f5f88df96b6639ad9d Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 11:21:14 -0600 Subject: [PATCH 073/489] update 'description' in issue 793bda8b9726b0336d97e856895907f8 --- 793bda8b9726b0336d97e856895907f8/description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 793bda8b9726b0336d97e856895907f8/description diff --git a/793bda8b9726b0336d97e856895907f8/description b/793bda8b9726b0336d97e856895907f8/description new file mode 100644 index 0000000..82c1c84 --- /dev/null +++ b/793bda8b9726b0336d97e856895907f8/description @@ -0,0 +1,4 @@ +`ent list` should have a consistent sort order + +Maybe first sort and group by State, and sort chronologically (by Issue +creation time) within each State grouping? From 19a727ee1773397ae262206fbf8e0f9078b58427 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 11:23:43 -0600 Subject: [PATCH 074/489] update 'description' in issue cb48ee85eb91c4061d60043d53fa68f8 --- .../comments/cb48ee85eb91c4061d60043d53fa68f8/description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 317ea8ccac1d414cde55771321bdec30/comments/cb48ee85eb91c4061d60043d53fa68f8/description diff --git a/317ea8ccac1d414cde55771321bdec30/comments/cb48ee85eb91c4061d60043d53fa68f8/description b/317ea8ccac1d414cde55771321bdec30/comments/cb48ee85eb91c4061d60043d53fa68f8/description new file mode 100644 index 0000000..0f940a7 --- /dev/null +++ b/317ea8ccac1d414cde55771321bdec30/comments/cb48ee85eb91c4061d60043d53fa68f8/description @@ -0,0 +1,4 @@ +Currently the `ent` program makes a worktree before considering what +command it's supposed to run. Which is nice because it simplifies each +command handler, but it's bad because different command handlers will +want different kinds of worktrees to implement this issue. From 8180ea9de2d74a7c0e766cd6c1afb7df16d54e56 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 12:30:54 -0600 Subject: [PATCH 075/489] update 'description' in issue af53c561b36e9b2709b939f81daee534 --- af53c561b36e9b2709b939f81daee534/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 af53c561b36e9b2709b939f81daee534/description diff --git a/af53c561b36e9b2709b939f81daee534/description b/af53c561b36e9b2709b939f81daee534/description new file mode 100644 index 0000000..e7da839 --- /dev/null +++ b/af53c561b36e9b2709b939f81daee534/description @@ -0,0 +1 @@ +use git author info to attribute issues and comments to people \ No newline at end of file From 2cacd628bed98fcfd1d0ed163ca2105de6ebef7b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 16:16:09 -0600 Subject: [PATCH 076/489] update 'state' in issue e089400e8a9e11fe9bf10d50b2f889d7 --- e089400e8a9e11fe9bf10d50b2f889d7/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e089400e8a9e11fe9bf10d50b2f889d7/state b/e089400e8a9e11fe9bf10d50b2f889d7/state index 505c028..348ebd9 100644 --- a/e089400e8a9e11fe9bf10d50b2f889d7/state +++ b/e089400e8a9e11fe9bf10d50b2f889d7/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 9a64c207d594b9d93693c9d8a6133c67658f835a Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 16:17:58 -0600 Subject: [PATCH 077/489] update 'state' in issue 793bda8b9726b0336d97e856895907f8 --- 793bda8b9726b0336d97e856895907f8/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 793bda8b9726b0336d97e856895907f8/state diff --git a/793bda8b9726b0336d97e856895907f8/state b/793bda8b9726b0336d97e856895907f8/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/793bda8b9726b0336d97e856895907f8/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 5702828299eeb509b6b3ef1217d3b82be00b4217 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 16:18:20 -0600 Subject: [PATCH 078/489] update 'state' in issue af53c561b36e9b2709b939f81daee534 --- af53c561b36e9b2709b939f81daee534/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 af53c561b36e9b2709b939f81daee534/state diff --git a/af53c561b36e9b2709b939f81daee534/state b/af53c561b36e9b2709b939f81daee534/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/af53c561b36e9b2709b939f81daee534/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 424aa103b2035d585ecebed235c17c3f333bde9f Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 16:30:13 -0600 Subject: [PATCH 079/489] update 'state' in issue 093e87e8049b93bfa2d8fcd544cae75f --- 093e87e8049b93bfa2d8fcd544cae75f/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 093e87e8049b93bfa2d8fcd544cae75f/state diff --git a/093e87e8049b93bfa2d8fcd544cae75f/state b/093e87e8049b93bfa2d8fcd544cae75f/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/093e87e8049b93bfa2d8fcd544cae75f/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From bc4e94785606ef6f6b3a19243a74cd59191b40a3 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 17:03:32 -0600 Subject: [PATCH 080/489] update 'assignee' in issue 093e87e8049b93bfa2d8fcd544cae75f --- 093e87e8049b93bfa2d8fcd544cae75f/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 093e87e8049b93bfa2d8fcd544cae75f/assignee diff --git a/093e87e8049b93bfa2d8fcd544cae75f/assignee b/093e87e8049b93bfa2d8fcd544cae75f/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/093e87e8049b93bfa2d8fcd544cae75f/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From 4ca8040370c950e89a21c8343db4c688f56328de Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 17:25:06 -0600 Subject: [PATCH 081/489] update 'assignee' in issue 75cefad80aacbf23fc7b9c24a75aa236 --- 75cefad80aacbf23fc7b9c24a75aa236/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 75cefad80aacbf23fc7b9c24a75aa236/assignee diff --git a/75cefad80aacbf23fc7b9c24a75aa236/assignee b/75cefad80aacbf23fc7b9c24a75aa236/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/75cefad80aacbf23fc7b9c24a75aa236/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From 219bcb6a390010644f7d42027ddfa95a3203cc25 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 17:25:13 -0600 Subject: [PATCH 082/489] update 'assignee' in issue 793bda8b9726b0336d97e856895907f8 --- 793bda8b9726b0336d97e856895907f8/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 793bda8b9726b0336d97e856895907f8/assignee diff --git a/793bda8b9726b0336d97e856895907f8/assignee b/793bda8b9726b0336d97e856895907f8/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/793bda8b9726b0336d97e856895907f8/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From e2546528bf303f14ddf563b6da393a11b4504ea3 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 17:25:20 -0600 Subject: [PATCH 083/489] update 'assignee' in issue af53c561b36e9b2709b939f81daee534 --- af53c561b36e9b2709b939f81daee534/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 af53c561b36e9b2709b939f81daee534/assignee diff --git a/af53c561b36e9b2709b939f81daee534/assignee b/af53c561b36e9b2709b939f81daee534/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/af53c561b36e9b2709b939f81daee534/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From 7e56f2979c4d0524fe70b3c48de9a707b3d5bed6 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 17:26:17 -0600 Subject: [PATCH 084/489] update 'description' in issue 093e87e8049b93bfa2d8fcd544cae75f --- 093e87e8049b93bfa2d8fcd544cae75f/description | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/093e87e8049b93bfa2d8fcd544cae75f/description b/093e87e8049b93bfa2d8fcd544cae75f/description index b6ac248..642810e 100644 --- a/093e87e8049b93bfa2d8fcd544cae75f/description +++ b/093e87e8049b93bfa2d8fcd544cae75f/description @@ -3,4 +3,5 @@ add optional 'assignee' to issue This is probably just another free-form text string for now, maybe something fancier in the future. -As part of this, teach `ent list FILTER` to filter by assignee. +NOTE: Teaching `ent list FILTER` to filter by assignee is a separate +issue. From b4306ce0dcad8c93ce00225d53b67c56459d7832 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 17:26:47 -0600 Subject: [PATCH 085/489] update 'description' in issue 9e69a30ad6965d7488514584c97ac63c --- 9e69a30ad6965d7488514584c97ac63c/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 9e69a30ad6965d7488514584c97ac63c/description diff --git a/9e69a30ad6965d7488514584c97ac63c/description b/9e69a30ad6965d7488514584c97ac63c/description new file mode 100644 index 0000000..0c241bc --- /dev/null +++ b/9e69a30ad6965d7488514584c97ac63c/description @@ -0,0 +1 @@ +teach `ent list FILTER` to filter by assignee From eb76e418f3ba3ff76d7e526fd01eb6291dc56039 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 17:27:47 -0600 Subject: [PATCH 086/489] update 'description' in issue bed47b2be016cc41eb43ef6d0acf1f8f --- bed47b2be016cc41eb43ef6d0acf1f8f/description | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 bed47b2be016cc41eb43ef6d0acf1f8f/description diff --git a/bed47b2be016cc41eb43ef6d0acf1f8f/description b/bed47b2be016cc41eb43ef6d0acf1f8f/description new file mode 100644 index 0000000..6b7ac88 --- /dev/null +++ b/bed47b2be016cc41eb43ef6d0acf1f8f/description @@ -0,0 +1,6 @@ +add some way to un-assign an issue + +This will probably involve removing the `assignee` file from the issue +directory, which will mean teaching the git code about removed files. + +Or maybe write the empty string to that file? From aaf947e11f091e9d1a025681e3a2943b43a0f2b1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 17:30:41 -0600 Subject: [PATCH 087/489] update 'description' in issue ae56208b6db64ccd9da31ce2bb1aad64 --- ae56208b6db64ccd9da31ce2bb1aad64/description | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 ae56208b6db64ccd9da31ce2bb1aad64/description diff --git a/ae56208b6db64ccd9da31ce2bb1aad64/description b/ae56208b6db64ccd9da31ce2bb1aad64/description new file mode 100644 index 0000000..0187de1 --- /dev/null +++ b/ae56208b6db64ccd9da31ce2bb1aad64/description @@ -0,0 +1,7 @@ +`ent sync`, ^C, now there's a stray worktree + +The worktree has the `entomologist-data` branch checked out in it, +so ent won't make another. + +Probably want a crash handler in `ent` to clean up any worktree we +have open. From be362517fb8c7185df7229dffe7058c717e908a1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 7 Jul 2025 23:45:03 -0600 Subject: [PATCH 088/489] give Comment a timestamp, display in chronological order This commit makes a couple of changes: - `ent show ISSUE` now displays the Issue's Comments in chronological order - the Comment struct now includes a timestamp, which is the Author Time of the oldest commit that touches the comment's directory - the Issue struct now stores its Comments in a sorted Vec, not in a HashMap - The Comment's uuid moved into the Comment struct itself, instead of being the key in the Issue's HashMap of Comments --- Cargo.toml | 1 + src/bin/ent/main.rs | 5 +++-- src/comment.rs | 13 +++++++++++-- src/git.rs | 28 ++++++++++++++++++++++++++++ src/issue.rs | 21 ++++++++++++--------- src/issues.rs | 20 ++++++++++---------- 6 files changed, 65 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fccb9c9..af271d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ log = ["dep:log", "dep:simple_logger"] [dependencies] anyhow = "1.0.95" +chrono = "0.4.41" clap = { version = "4.5.26", features = ["derive"] } log = { version = "0.4.27", optional = true } rand = "0.9.1" diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 4ecc596..2d0f56a 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -112,9 +112,10 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } println!(""); println!("{}", issue.description); - for (uuid, comment) in issue.comments.iter() { + for comment in &issue.comments { println!(""); - println!("comment: {}", uuid); + println!("comment: {}", comment.uuid); + println!("timestamp: {}", comment.timestamp); println!("{}", comment.description); } } diff --git a/src/comment.rs b/src/comment.rs index ab2ced9..8cd00a0 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -2,6 +2,8 @@ use std::io::Write; #[derive(Debug, PartialEq)] pub struct Comment { + pub uuid: String, + pub timestamp: chrono::DateTime, pub description: String, /// This is the directory that the comment lives in. Only used @@ -39,12 +41,16 @@ impl Comment { } } } - if description == None { return Err(CommentError::CommentParseError); } + let timestamp = crate::git::git_log_oldest_timestamp(comment_dir)?; + let dir = std::path::PathBuf::from(comment_dir); + Ok(Self { + uuid: String::from(dir.file_name().unwrap().to_string_lossy()), + timestamp, description: description.unwrap(), dir: std::path::PathBuf::from(comment_dir), }) @@ -95,8 +101,11 @@ mod tests { std::path::Path::new("test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/comments/9055dac36045fe36545bed7ae7b49347"); let comment = Comment::new_from_dir(comment_dir).unwrap(); let expected = Comment { + uuid: String::from("9055dac36045fe36545bed7ae7b49347"), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00") + .unwrap() + .with_timezone(&chrono::Local), description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561\n\nIt has multiple lines\n"), - dir: std::path::PathBuf::from(comment_dir), }; assert_eq!(comment, expected); diff --git a/src/git.rs b/src/git.rs index 30f703e..d26293a 100644 --- a/src/git.rs +++ b/src/git.rs @@ -4,6 +4,8 @@ use std::io::Write; pub enum GitError { #[error(transparent)] StdIoError(#[from] std::io::Error), + #[error(transparent)] + ParseIntError(#[from] std::num::ParseIntError), #[error("Oops, something went wrong")] Oops, } @@ -180,6 +182,32 @@ pub fn sync(dir: &std::path::Path, remote: &str, branch: &str) -> Result<(), Git Ok(()) } +pub fn git_log_oldest_timestamp( + path: &std::path::Path, +) -> Result, 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", + &path.file_name().unwrap().to_string_lossy(), + ]) + .current_dir(&git_dir) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + let timestamp_str = std::str::from_utf8(&result.stdout).unwrap(); + let timestamp_i64 = timestamp_str.parse::()?; + let timestamp = chrono::DateTime::from_timestamp(timestamp_i64, 0) + .unwrap() + .with_timezone(&chrono::Local); + Ok(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 5f9664f..cdbac1f 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -23,7 +23,7 @@ pub struct Issue { pub description: String, pub state: State, pub dependencies: Option>, - pub comments: std::collections::HashMap, + pub comments: Vec, /// This is the directory that the issue lives in. Only used /// internally by the entomologist library. @@ -87,7 +87,7 @@ impl Issue { let mut description: Option = None; let mut state = State::New; // default state, if not specified in the issue let mut dependencies: Option> = None; - let mut comments = std::collections::HashMap::::new(); + let mut comments = Vec::::new(); for direntry in dir.read_dir()? { if let Ok(direntry) = direntry { @@ -129,16 +129,16 @@ impl Issue { } fn read_comments( - comments: &mut std::collections::HashMap, + comments: &mut Vec, dir: &std::path::Path, ) -> Result<(), IssueError> { for direntry in dir.read_dir()? { if let Ok(direntry) = direntry { - let uuid = direntry.file_name(); let comment = crate::comment::Comment::new_from_dir(&direntry.path())?; - comments.insert(String::from(uuid.to_string_lossy()), comment); + comments.push(comment); } } + comments.sort_by(|a, b| a.timestamp.cmp(&b.timestamp)); Ok(()) } @@ -150,10 +150,13 @@ impl Issue { } let rnd: u128 = rand::random(); - dir.push(&format!("{:032x}", rnd)); + let uuid = format!("{:032x}", rnd); + dir.push(&uuid); std::fs::create_dir(&dir)?; Ok(crate::comment::Comment { + uuid, + timestamp: chrono::Local::now(), description: String::from(""), // FIXME dir, }) @@ -168,7 +171,7 @@ impl Issue { description: String::from(""), // FIXME: kind of bogus to use the empty string as None state: State::New, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir: issue_dir, }) } @@ -244,7 +247,7 @@ mod tests { description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), state: State::New, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir: std::path::PathBuf::from(issue_dir), }; assert_eq!(issue, expected); @@ -258,7 +261,7 @@ mod tests { description: String::from("minimal"), state: State::InProgress, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir: std::path::PathBuf::from(issue_dir), }; assert_eq!(issue, expected); diff --git a/src/issues.rs b/src/issues.rs index 28df2e9..06af867 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -99,7 +99,7 @@ mod tests { description: String::from("minimal"), state: crate::issue::State::InProgress, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir, }, ); @@ -113,7 +113,7 @@ mod tests { description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), state: crate::issue::State::New, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir, } ); @@ -136,7 +136,7 @@ mod tests { description: String::from("oh yeah we got titles"), state: crate::issue::State::Done, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir, }, ); @@ -148,12 +148,12 @@ mod tests { let comment_uuid = String::from("9055dac36045fe36545bed7ae7b49347"); comment_dir.push("comments"); comment_dir.push(&comment_uuid); - let mut expected_comments = - std::collections::HashMap::::new(); - expected_comments.insert( - String::from(&comment_uuid), + let mut expected_comments = Vec::::new(); + expected_comments.push( crate::comment::Comment { + uuid: comment_uuid, description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561\n\nIt has multiple lines\n"), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00").unwrap().with_timezone(&chrono::Local), dir: std::path::PathBuf::from(comment_dir), } ); @@ -186,7 +186,7 @@ mod tests { description: String::from("oh yeah we got titles\n"), state: crate::issue::State::Done, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir, }, ); @@ -200,7 +200,7 @@ mod tests { description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), state: crate::issue::State::WontDo, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir, }, ); @@ -217,7 +217,7 @@ mod tests { crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), crate::issue::IssueHandle::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), ]), - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir, }, ); From a2c7ce34a363e15b3dac2b0160fb3a449a750900 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 14:45:17 -0600 Subject: [PATCH 089/489] fix git::git_log_oldest_timestamp() when there are multiple log entries --- src/git.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/git.rs b/src/git.rs index d26293a..28e8400 100644 --- a/src/git.rs +++ b/src/git.rs @@ -191,6 +191,7 @@ pub fn git_log_oldest_timestamp( .args([ "log", "--pretty=format:%at", + "--", &path.file_name().unwrap().to_string_lossy(), ]) .current_dir(&git_dir) @@ -201,7 +202,8 @@ pub fn git_log_oldest_timestamp( return Err(GitError::Oops); } let timestamp_str = std::str::from_utf8(&result.stdout).unwrap(); - let timestamp_i64 = timestamp_str.parse::()?; + let timestamp_last = timestamp_str.split("\n").last().unwrap(); + let timestamp_i64 = timestamp_last.parse::()?; let timestamp = chrono::DateTime::from_timestamp(timestamp_i64, 0) .unwrap() .with_timezone(&chrono::Local); From 9c5f5cbb14522ddc350fe998db969ace66f185b4 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 18:48:13 -0600 Subject: [PATCH 090/489] update 'state' in issue 093e87e8049b93bfa2d8fcd544cae75f --- 093e87e8049b93bfa2d8fcd544cae75f/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/093e87e8049b93bfa2d8fcd544cae75f/state b/093e87e8049b93bfa2d8fcd544cae75f/state index 505c028..348ebd9 100644 --- a/093e87e8049b93bfa2d8fcd544cae75f/state +++ b/093e87e8049b93bfa2d8fcd544cae75f/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 38e9e57a1201b0f7e98481ffbc6f885ff7da7514 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 18:48:23 -0600 Subject: [PATCH 091/489] update 'state' in issue 793bda8b9726b0336d97e856895907f8 --- 793bda8b9726b0336d97e856895907f8/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/793bda8b9726b0336d97e856895907f8/state b/793bda8b9726b0336d97e856895907f8/state index 505c028..348ebd9 100644 --- a/793bda8b9726b0336d97e856895907f8/state +++ b/793bda8b9726b0336d97e856895907f8/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 9faebd4a23123b51dac037aa6245821357db34a0 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 18:48:37 -0600 Subject: [PATCH 092/489] update 'state' in issue af53c561b36e9b2709b939f81daee534 --- af53c561b36e9b2709b939f81daee534/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/af53c561b36e9b2709b939f81daee534/state b/af53c561b36e9b2709b939f81daee534/state index 505c028..348ebd9 100644 --- a/af53c561b36e9b2709b939f81daee534/state +++ b/af53c561b36e9b2709b939f81daee534/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 06d1042c88b87344b87d0990f4f71a9668161b9b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 18:49:40 -0600 Subject: [PATCH 093/489] update 'description' in issue 9e81dce91164a3ee26c3214ee2d9d809 --- .../comments/9e81dce91164a3ee26c3214ee2d9d809/description | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 75cefad80aacbf23fc7b9c24a75aa236/comments/9e81dce91164a3ee26c3214ee2d9d809/description diff --git a/75cefad80aacbf23fc7b9c24a75aa236/comments/9e81dce91164a3ee26c3214ee2d9d809/description b/75cefad80aacbf23fc7b9c24a75aa236/comments/9e81dce91164a3ee26c3214ee2d9d809/description new file mode 100644 index 0000000..e69de29 From eafa2a17bd897e14dc9b9036640df775cab38426 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 18:53:10 -0600 Subject: [PATCH 094/489] update 'description' in issue a5ac277614ea4d13f78031abb25ea7d6 --- a5ac277614ea4d13f78031abb25ea7d6/description | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 a5ac277614ea4d13f78031abb25ea7d6/description diff --git a/a5ac277614ea4d13f78031abb25ea7d6/description b/a5ac277614ea4d13f78031abb25ea7d6/description new file mode 100644 index 0000000..9c6996e --- /dev/null +++ b/a5ac277614ea4d13f78031abb25ea7d6/description @@ -0,0 +1,7 @@ +`ent new` and `ent comment`: detect empty issue descriptions & comments + +If the user changes their mind and saves an empty file, don't make a +new empty comment. + +For an embarrassing example, see comment 9e81dce91164a3ee26c3214ee2d9d809 +on issue 75cefad80aacbf23fc7b9c24a75aa236. From be84a6f7157544200b887e3dbeb9f2a789966a55 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 18:53:51 -0600 Subject: [PATCH 095/489] update 'assignee' in issue 9e69a30ad6965d7488514584c97ac63c --- 9e69a30ad6965d7488514584c97ac63c/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 9e69a30ad6965d7488514584c97ac63c/assignee diff --git a/9e69a30ad6965d7488514584c97ac63c/assignee b/9e69a30ad6965d7488514584c97ac63c/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/9e69a30ad6965d7488514584c97ac63c/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From b74f235b06367f5522ad4029035204d169c76666 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 18:53:58 -0600 Subject: [PATCH 096/489] update 'state' in issue 9e69a30ad6965d7488514584c97ac63c --- 9e69a30ad6965d7488514584c97ac63c/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 9e69a30ad6965d7488514584c97ac63c/state diff --git a/9e69a30ad6965d7488514584c97ac63c/state b/9e69a30ad6965d7488514584c97ac63c/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/9e69a30ad6965d7488514584c97ac63c/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 7acd94f7c0e403260b336a7a7998d400b69c14d2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 14:08:21 -0600 Subject: [PATCH 097/489] add author to Comment --- src/bin/ent/main.rs | 1 + src/comment.rs | 4 ++++ src/git.rs | 22 ++++++++++++++++++++++ src/issue.rs | 1 + src/issues.rs | 3 ++- 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 2d0f56a..ac15eb4 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -115,6 +115,7 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( for comment in &issue.comments { println!(""); println!("comment: {}", comment.uuid); + println!("author: {}", comment.author); println!("timestamp: {}", comment.timestamp); println!("{}", comment.description); } diff --git a/src/comment.rs b/src/comment.rs index 8cd00a0..d6ed66e 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -3,6 +3,7 @@ use std::io::Write; #[derive(Debug, PartialEq)] pub struct Comment { pub uuid: String, + pub author: String, pub timestamp: chrono::DateTime, pub description: String, @@ -45,11 +46,13 @@ impl Comment { return Err(CommentError::CommentParseError); } + let author = crate::git::git_log_oldest_author(comment_dir)?; let timestamp = crate::git::git_log_oldest_timestamp(comment_dir)?; let dir = std::path::PathBuf::from(comment_dir); Ok(Self { uuid: String::from(dir.file_name().unwrap().to_string_lossy()), + author, timestamp, description: description.unwrap(), dir: std::path::PathBuf::from(comment_dir), @@ -102,6 +105,7 @@ mod tests { let comment = Comment::new_from_dir(comment_dir).unwrap(); let expected = Comment { uuid: String::from("9055dac36045fe36545bed7ae7b49347"), + author: String::from("Sebastian Kuzminsky "), timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00") .unwrap() .with_timezone(&chrono::Local), diff --git a/src/git.rs b/src/git.rs index 28e8400..6018392 100644 --- a/src/git.rs +++ b/src/git.rs @@ -210,6 +210,28 @@ pub fn git_log_oldest_timestamp( Ok(timestamp) } +pub fn git_log_oldest_author(path: &std::path::Path) -> Result { + let mut git_dir = std::path::PathBuf::from(path); + git_dir.pop(); + let result = std::process::Command::new("git") + .args([ + "log", + "--pretty=format:%an <%ae>", + "--", + &path.file_name().unwrap().to_string_lossy(), + ]) + .current_dir(&git_dir) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + let author_str = std::str::from_utf8(&result.stdout).unwrap(); + let author_last = author_str.split("\n").last().unwrap(); + Ok(String::from(author_last)) +} + 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 cdbac1f..0e83297 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -156,6 +156,7 @@ impl Issue { Ok(crate::comment::Comment { uuid, + author: String::from("Sebastian Kuzminsky "), timestamp: chrono::Local::now(), description: String::from(""), // FIXME dir, diff --git a/src/issues.rs b/src/issues.rs index 06af867..3be405d 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -152,8 +152,9 @@ mod tests { expected_comments.push( crate::comment::Comment { uuid: comment_uuid, - description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561\n\nIt has multiple lines\n"), + author: String::from("Sebastian Kuzminsky "), timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00").unwrap().with_timezone(&chrono::Local), + description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561\n\nIt has multiple lines\n"), dir: std::path::PathBuf::from(comment_dir), } ); From e8b37cd86a93aae72ee506fec77133027bc4b0c1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 14:18:31 -0600 Subject: [PATCH 098/489] add author and timestamp to Issue --- src/bin/ent/main.rs | 2 ++ src/issue.rs | 27 ++++++++++++++++++++++----- src/issues.rs | 42 +++++++++++++++++++++++++++++++++++------- 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index ac15eb4..81870e3 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -106,6 +106,8 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( match issues.get_issue(issue_id) { Some(issue) => { println!("issue {}", issue_id); + println!("author: {}", issue.author); + println!("timestamp: {}", issue.timestamp); println!("state: {:?}", issue.state); if let Some(dependencies) = &issue.dependencies { println!("dependencies: {:?}", dependencies); diff --git a/src/issue.rs b/src/issue.rs index 0e83297..97aa8f1 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -20,9 +20,11 @@ pub type IssueHandle = String; #[derive(Debug, PartialEq)] pub struct Issue { - pub description: String, + pub author: String, + pub timestamp: chrono::DateTime, pub state: State, pub dependencies: Option>, + pub description: String, pub comments: Vec, /// This is the directory that the issue lives in. Only used @@ -119,10 +121,15 @@ impl Issue { return Err(IssueError::IssueParseError); } + let author = crate::git::git_log_oldest_author(dir)?; + let timestamp = crate::git::git_log_oldest_timestamp(dir)?; + Ok(Self { - description: description.unwrap(), + author, + timestamp, state: state, dependencies, + description: description.unwrap(), comments, dir: std::path::PathBuf::from(dir), }) @@ -169,9 +176,11 @@ impl Issue { issue_dir.push(&format!("{:032x}", rnd)); std::fs::create_dir(&issue_dir)?; Ok(Self { - description: String::from(""), // FIXME: kind of bogus to use the empty string as None + author: String::from(""), + timestamp: chrono::Local::now(), state: State::New, dependencies: None, + description: String::from(""), // FIXME: kind of bogus to use the empty string as None comments: Vec::::new(), dir: issue_dir, }) @@ -245,9 +254,13 @@ mod tests { let issue_dir = std::path::Path::new("test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/"); let issue = Issue::new_from_dir(issue_dir).unwrap(); let expected = Issue { - description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), + author: String::from("Sebastian Kuzminsky "), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + .unwrap() + .with_timezone(&chrono::Local), state: State::New, dependencies: None, + description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), comments: Vec::::new(), dir: std::path::PathBuf::from(issue_dir), }; @@ -259,9 +272,13 @@ mod tests { let issue_dir = std::path::Path::new("test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/"); let issue = Issue::new_from_dir(issue_dir).unwrap(); let expected = Issue { - description: String::from("minimal"), + author: String::from("Sebastian Kuzminsky "), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + .unwrap() + .with_timezone(&chrono::Local), state: State::InProgress, dependencies: None, + description: String::from("minimal"), comments: Vec::::new(), dir: std::path::PathBuf::from(issue_dir), }; diff --git a/src/issues.rs b/src/issues.rs index 3be405d..ff6accb 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -96,9 +96,13 @@ mod tests { expected.add_issue( uuid, crate::issue::Issue { - description: String::from("minimal"), + author: String::from("Sebastian Kuzminsky "), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + .unwrap() + .with_timezone(&chrono::Local), state: crate::issue::State::InProgress, dependencies: None, + description: String::from("minimal"), comments: Vec::::new(), dir, }, @@ -110,9 +114,13 @@ mod tests { expected.add_issue( uuid, crate::issue::Issue { - description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), + author: String::from("Sebastian Kuzminsky "), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + .unwrap() + .with_timezone(&chrono::Local), state: crate::issue::State::New, dependencies: None, + description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), comments: Vec::::new(), dir, } @@ -133,9 +141,13 @@ mod tests { expected.add_issue( uuid, crate::issue::Issue { - description: String::from("oh yeah we got titles"), + author: String::from("Sebastian Kuzminsky "), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") + .unwrap() + .with_timezone(&chrono::Local), state: crate::issue::State::Done, dependencies: None, + description: String::from("oh yeah we got titles"), comments: Vec::::new(), dir, }, @@ -161,9 +173,13 @@ mod tests { expected.add_issue( uuid, crate::issue::Issue { - description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), + author: String::from("Sebastian Kuzminsky "), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") + .unwrap() + .with_timezone(&chrono::Local), state: crate::issue::State::WontDo, dependencies: None, + description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), comments: expected_comments, dir, }, @@ -184,9 +200,13 @@ mod tests { expected.add_issue( uuid, crate::issue::Issue { - description: String::from("oh yeah we got titles\n"), + author: String::from("sigil-03 "), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") + .unwrap() + .with_timezone(&chrono::Local), state: crate::issue::State::Done, dependencies: None, + description: String::from("oh yeah we got titles\n"), comments: Vec::::new(), dir, }, @@ -198,9 +218,13 @@ mod tests { expected.add_issue( uuid, crate::issue::Issue { - description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), + author: String::from("sigil-03 "), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") + .unwrap() + .with_timezone(&chrono::Local), state: crate::issue::State::WontDo, dependencies: None, + description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), comments: Vec::::new(), dir, }, @@ -212,12 +236,16 @@ mod tests { expected.add_issue( uuid, crate::issue::Issue { - description: String::from("issue with dependencies\n\na test has begun\nfor dependencies we seek\nintertwining life"), + author: String::from("sigil-03 "), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") + .unwrap() + .with_timezone(&chrono::Local), state: crate::issue::State::WontDo, dependencies: Some(vec![ crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), crate::issue::IssueHandle::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), ]), + description: String::from("issue with dependencies\n\na test has begun\nfor dependencies we seek\nintertwining life"), comments: Vec::::new(), dir, }, From ba57f629e38995d1973d9447e8d675b83b7af129 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 16:09:45 -0600 Subject: [PATCH 099/489] make `ent list` sort issues first by state, then by ctime --- src/bin/ent/main.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 81870e3..aa15752 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -66,11 +66,44 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( let issues = entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; let filter = entomologist::parse_filter(filter)?; + let mut uuids_by_state = std::collections::HashMap::< + entomologist::issue::State, + Vec<&entomologist::issue::IssueHandle>, + >::new(); for (uuid, issue) in issues.issues.iter() { if filter.include_states.contains(&issue.state) { - println!("{} {} ({:?})", uuid, issue.title(), issue.state); + uuids_by_state + .entry(issue.state.clone()) + .or_default() + .push(uuid); } } + + use entomologist::issue::State; + for state in [ + State::InProgress, + State::Blocked, + State::Backlog, + State::New, + State::Done, + State::WontDo, + ] { + let these_uuids = uuids_by_state.entry(state.clone()).or_default(); + if these_uuids.len() == 0 { + continue; + } + these_uuids.sort_by(|a_id, b_id| { + let a = issues.issues.get(*a_id).unwrap(); + let b = issues.issues.get(*b_id).unwrap(); + a.timestamp.cmp(&b.timestamp) + }); + println!("{:?}:", state); + for uuid in these_uuids { + let issue = issues.issues.get(*uuid).unwrap(); + println!("{} {}", uuid, issue.title()); + } + println!(""); + } } Commands::New { From 400e0ca26f893697ad5b3691b5117d37047a3fbe Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 16:20:44 -0600 Subject: [PATCH 100/489] ent list: show comment count for each issue --- src/bin/ent/main.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index aa15752..952d178 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -100,7 +100,12 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( println!("{:?}:", state); for uuid in these_uuids { let issue = issues.issues.get(*uuid).unwrap(); - println!("{} {}", uuid, issue.title()); + let num_comments = issue.comments.len(); + if num_comments == 0 { + println!("{} {}", uuid, issue.title()); + } else { + println!("{} 🗩 {} {}", uuid, num_comments, issue.title()); + } } println!(""); } From 645062d10cbdd09c976fc675c6113165e814bef1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 16:29:00 -0600 Subject: [PATCH 101/489] add optional 'assignee' to Issue --- src/bin/ent/main.rs | 3 +++ src/issue.rs | 10 ++++++++++ src/issues.rs | 7 +++++++ .../7792b063eef6d33e7da5dc1856750c149ba678c6/assignee | 1 + 4 files changed, 21 insertions(+) create mode 100644 test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/assignee diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 952d178..cb8eb9d 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -150,6 +150,9 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( if let Some(dependencies) = &issue.dependencies { println!("dependencies: {:?}", dependencies); } + if let Some(assignee) = &issue.assignee { + println!("assignee: {}", assignee); + } println!(""); println!("{}", issue.description); for comment in &issue.comments { diff --git a/src/issue.rs b/src/issue.rs index 97aa8f1..096e497 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -24,6 +24,7 @@ pub struct Issue { pub timestamp: chrono::DateTime, pub state: State, pub dependencies: Option>, + pub assignee: Option, pub description: String, pub comments: Vec, @@ -90,6 +91,7 @@ impl Issue { let mut state = State::New; // default state, if not specified in the issue let mut dependencies: Option> = None; let mut comments = Vec::::new(); + let mut assignee: Option = None; for direntry in dir.read_dir()? { if let Ok(direntry) = direntry { @@ -99,6 +101,10 @@ impl Issue { } else if file_name == "state" { let state_string = std::fs::read_to_string(direntry.path())?; state = State::from_str(state_string.trim())?; + } else if file_name == "assignee" { + assignee = Some(String::from( + std::fs::read_to_string(direntry.path())?.trim(), + )); } else if file_name == "dependencies" { let dep_strings = std::fs::read_to_string(direntry.path())?; let deps: Vec = dep_strings @@ -129,6 +135,7 @@ impl Issue { timestamp, state: state, dependencies, + assignee, description: description.unwrap(), comments, dir: std::path::PathBuf::from(dir), @@ -180,6 +187,7 @@ impl Issue { timestamp: chrono::Local::now(), state: State::New, dependencies: None, + assignee: None, description: String::from(""), // FIXME: kind of bogus to use the empty string as None comments: Vec::::new(), dir: issue_dir, @@ -260,6 +268,7 @@ mod tests { .with_timezone(&chrono::Local), state: State::New, dependencies: None, + assignee: None, description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), comments: Vec::::new(), dir: std::path::PathBuf::from(issue_dir), @@ -278,6 +287,7 @@ mod tests { .with_timezone(&chrono::Local), state: State::InProgress, dependencies: None, + assignee: Some(String::from("beep boop")), description: String::from("minimal"), comments: Vec::::new(), dir: std::path::PathBuf::from(issue_dir), diff --git a/src/issues.rs b/src/issues.rs index ff6accb..16dab55 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -102,6 +102,7 @@ mod tests { .with_timezone(&chrono::Local), state: crate::issue::State::InProgress, dependencies: None, + assignee: Some(String::from("beep boop")), description: String::from("minimal"), comments: Vec::::new(), dir, @@ -120,6 +121,7 @@ mod tests { .with_timezone(&chrono::Local), state: crate::issue::State::New, dependencies: None, + assignee: None, description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), comments: Vec::::new(), dir, @@ -147,6 +149,7 @@ mod tests { .with_timezone(&chrono::Local), state: crate::issue::State::Done, dependencies: None, + assignee: None, description: String::from("oh yeah we got titles"), comments: Vec::::new(), dir, @@ -179,6 +182,7 @@ mod tests { .with_timezone(&chrono::Local), state: crate::issue::State::WontDo, dependencies: None, + assignee: None, description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), comments: expected_comments, dir, @@ -206,6 +210,7 @@ mod tests { .with_timezone(&chrono::Local), state: crate::issue::State::Done, dependencies: None, + assignee: None, description: String::from("oh yeah we got titles\n"), comments: Vec::::new(), dir, @@ -224,6 +229,7 @@ mod tests { .with_timezone(&chrono::Local), state: crate::issue::State::WontDo, dependencies: None, + assignee: None, description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), comments: Vec::::new(), dir, @@ -245,6 +251,7 @@ mod tests { crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), crate::issue::IssueHandle::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), ]), + assignee: None, description: String::from("issue with dependencies\n\na test has begun\nfor dependencies we seek\nintertwining life"), comments: Vec::::new(), dir, diff --git a/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/assignee b/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/assignee new file mode 100644 index 0000000..fae06e3 --- /dev/null +++ b/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/assignee @@ -0,0 +1 @@ +beep boop From d21b811bee46aa0749f964a74fead2923b40a060 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 17:16:17 -0600 Subject: [PATCH 102/489] add `ent assign ISSUE PERSON` --- src/bin/ent/main.rs | 37 +++++++++++++++++++++++++++++++++++++ src/issue.rs | 9 +++++++++ 2 files changed, 46 insertions(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index cb8eb9d..5237281 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -58,6 +58,12 @@ enum Commands { #[arg(default_value_t = String::from("origin"))] remote: String, }, + + /// Get or set the Assignee field of an Issue. + Assign { + issue_id: String, + new_assignee: Option, + }, } fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { @@ -234,6 +240,37 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( entomologist::git::sync(issues_dir, remote, branch)?; println!("synced {:?} with {:?}", branch, remote); } + + Commands::Assign { + issue_id, + new_assignee, + } => { + let mut issues = + entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; + let Some(issue) = issues.issues.get_mut(issue_id) else { + return Err(anyhow::anyhow!("issue {} not found", issue_id)); + }; + match (&issue.assignee, new_assignee) { + (Some(old_assignee), Some(new_assignee)) => { + println!("issue: {}", issue_id); + println!("assignee: {} -> {}", old_assignee, new_assignee); + issue.set_assignee(new_assignee)?; + } + (Some(old_assignee), None) => { + println!("issue: {}", issue_id); + println!("assignee: {}", old_assignee); + } + (None, Some(new_assignee)) => { + println!("issue: {}", issue_id); + println!("assignee: None -> {}", new_assignee); + issue.set_assignee(new_assignee)?; + } + (None, None) => { + println!("issue: {}", issue_id); + println!("assignee: None"); + } + } + } } Ok(()) diff --git a/src/issue.rs b/src/issue.rs index 096e497..68cb8f9 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -251,6 +251,15 @@ impl Issue { self.state = State::from_str(state_string.trim())?; Ok(()) } + + pub fn set_assignee(&mut self, new_assignee: &str) -> Result<(), IssueError> { + let mut assignee_filename = std::path::PathBuf::from(&self.dir); + assignee_filename.push("assignee"); + let mut assignee_file = std::fs::File::create(&assignee_filename)?; + write!(assignee_file, "{}", new_assignee)?; + crate::git::git_commit_file(&assignee_filename)?; + Ok(()) + } } #[cfg(test)] From a676bd9cdd6876a7de520b5751576b0945f5c8ce Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 17:31:10 -0600 Subject: [PATCH 103/489] ent list: show assignee, if any --- src/bin/ent/main.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 5237281..2a8f1f4 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -106,12 +106,15 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( println!("{:?}:", state); for uuid in these_uuids { let issue = issues.issues.get(*uuid).unwrap(); - let num_comments = issue.comments.len(); - if num_comments == 0 { - println!("{} {}", uuid, issue.title()); - } else { - println!("{} 🗩 {} {}", uuid, num_comments, issue.title()); - } + let comments = match issue.comments.len() { + 0 => String::from(" "), + n => format!("🗩 {}", n), + }; + let assignee = match &issue.assignee { + Some(assignee) => format!(" (👉 {})", assignee), + None => String::from(""), + }; + println!("{} {} {}{}", uuid, comments, issue.title(), assignee); } println!(""); } From df7b5c6aa4a00dfca66dc802f1e813f23f27a5b9 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 8 Jul 2025 22:21:09 -0600 Subject: [PATCH 104/489] add `ent list` filter by assignee I'm not sure about the filter format... There are two independent filters: "state" and "assignee". The "state" filter defaults to including issues whose state is InProgress, Blocked, Backlog, or New. The "assignee" filter defaults to including all issues, assigned or not. The two filters can be independently overridden by the `ent list FILTER` command. FILTER is a string containing chunks separated by ":", like the PATH environment variable. Each chunk is of the form "name=value[,value...]". "name" can be either "state" or "assignee". The "value" arguments to the "state" filter must be one of the valid states, or it's a parse error. The "value" arguments to the "assignee" filter are used to string-compare against the issues "assignee" field, exact matches are accepted and everything else is rejected. A special assignee filter of the empty string matches issues that don't have an assignee. Some examples: * `ent list` shows issues in the states listed above, and don't filter based on assignee at all. * `ent list assignee=seb` shows issues in the states listed above, but only if the assignee is "seb". * `ent list assignee=seb,` shows issues in the states listed above, but only if the assignee is "seb" or if there is no assignee. * `ent list state=done` shows all issues in the Done state. * `ent list state=done:assignee=seb` shows issues in the Done state that are assigned to "seb". --- src/bin/ent/main.rs | 24 ++++++++++++++----- src/lib.rs | 56 +++++++++++++++++++++++++++++++++------------ 2 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 2a8f1f4..2892d6a 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -71,18 +71,30 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( Commands::List { filter } => { let issues = entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; - let filter = entomologist::parse_filter(filter)?; + let filter = entomologist::Filter::new_from_str(filter)?; let mut uuids_by_state = std::collections::HashMap::< entomologist::issue::State, Vec<&entomologist::issue::IssueHandle>, >::new(); for (uuid, issue) in issues.issues.iter() { - if filter.include_states.contains(&issue.state) { - uuids_by_state - .entry(issue.state.clone()) - .or_default() - .push(uuid); + if !filter.include_states.contains(&issue.state) { + continue; } + if filter.include_assignees.len() > 0 { + let assignee = match &issue.assignee { + Some(assignee) => assignee, + None => "", + }; + if !filter.include_assignees.contains(assignee) { + continue; + } + } + + // This issue passed all the filters, include it in list. + uuids_by_state + .entry(issue.state.clone()) + .or_default() + .push(uuid); } use entomologist::issue::State; diff --git a/src/lib.rs b/src/lib.rs index 77a00d9..b28fb74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,24 +17,50 @@ pub enum ParseFilterError { // i'm starting with obvious easy things. Chumsky looks appealing but // more research is needed. #[derive(Debug)] -pub struct Filter { +pub struct Filter<'a> { pub include_states: std::collections::HashSet, + pub include_assignees: std::collections::HashSet<&'a str>, } -// Parses a filter description matching "state=STATE[,STATE*]" -pub fn parse_filter(filter_str: &str) -> Result { - let tokens: Vec<&str> = filter_str.split("=").collect(); - if tokens.len() != 2 { - return Err(ParseFilterError::ParseError); - } - if tokens[0] != "state" { - return Err(ParseFilterError::ParseError); - } +impl<'a> Filter<'a> { + pub fn new_from_str(filter_str: &'a str) -> Result, ParseFilterError> { + use crate::issue::State; + let mut f = Filter { + include_states: std::collections::HashSet::::from([ + State::InProgress, + State::Blocked, + State::Backlog, + State::New, + ]), + include_assignees: std::collections::HashSet::<&'a str>::new(), + }; - let mut include_states = std::collections::HashSet::::new(); - for s in tokens[1].split(",") { - include_states.insert(crate::issue::State::from_str(s)?); - } + for filter_chunk_str in filter_str.split(":") { + let tokens: Vec<&str> = filter_chunk_str.split("=").collect(); + if tokens.len() != 2 { + return Err(ParseFilterError::ParseError); + } - Ok(Filter { include_states }) + match tokens[0] { + "state" => { + f.include_states.clear(); + for s in tokens[1].split(",") { + f.include_states.insert(crate::issue::State::from_str(s)?); + } + } + "assignee" => { + f.include_assignees.clear(); + for s in tokens[1].split(",") { + f.include_assignees.insert(s); + } + } + _ => { + println!("unknown filter chunk '{}'", filter_chunk_str); + return Err(ParseFilterError::ParseError); + } + } + } + + Ok(f) + } } From 49f473c5230795bdc88ee2c093344ee0a2ac4f2c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 10:25:04 -0600 Subject: [PATCH 105/489] update 'description' in issue 91b801a9b6b77cca66ae5ab0b7d97cca --- .../description | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 b738f2842db428df1b4aad0192a7f36c/comments/91b801a9b6b77cca66ae5ab0b7d97cca/description diff --git a/b738f2842db428df1b4aad0192a7f36c/comments/91b801a9b6b77cca66ae5ab0b7d97cca/description b/b738f2842db428df1b4aad0192a7f36c/comments/91b801a9b6b77cca66ae5ab0b7d97cca/description new file mode 100644 index 0000000..b3fd943 --- /dev/null +++ b/b738f2842db428df1b4aad0192a7f36c/comments/91b801a9b6b77cca66ae5ab0b7d97cca/description @@ -0,0 +1,38 @@ +# `ent list FILTER` filter format + +I'm not sure about the filter format, but here are some notes... + +There are (as of df7b5c6aa4a00dfca66dc802f1e813f23f27a5b9) two independent +filters: "state" and "assignee". The "state" filter defaults to +including issues whose state is InProgress, Blocked, Backlog, or New. +The "assignee" filter defaults to including all issues, assigned or not. + +The two filters can be independently overridden by the `ent list +FILTER` command. FILTER is a string containing "chunks" separated by +":", like the PATH environment variable. Each chunk is of the form +"name=value[,value...]". "name" can currently be either "state" or +"assignee", and we can add more in the future (e.g "tag" or "ctime"). + +The "value" arguments to the "state" filter must be one of the valid +states, or it's a parse error. + +The "value" arguments to the "assignee" filter are used to +string-compare against the issues "assignee" field, exact matches are +accepted and everything else is rejected. A special assignee filter of +the empty string matches issues that don't have an assignee. + +Some examples: + +* `ent list` shows issues in the states listed above, and don't filter + based on assignee at all. + +* `ent list assignee=seb` shows issues in the states listed above, + but only if the assignee is "seb". + +* `ent list assignee=seb,` shows issues in the states listed above, + but only if the assignee is "seb" or if there is no assignee. + +* `ent list state=done` shows all issues in the Done state. + +* `ent list state=inprogress:assignee=seb` shows issues in the InProgress + state that are assigned to "seb". From 83c7578cfcdfb84ef2f0fe2218f8ed6f02e34252 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 10:26:27 -0600 Subject: [PATCH 106/489] update 'description' in issue 87919ac6e79521732b0ec65fa7f79853 --- .../comments/87919ac6e79521732b0ec65fa7f79853/description | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 7da3bd5b72de0a05936b094db5d24304/comments/87919ac6e79521732b0ec65fa7f79853/description diff --git a/7da3bd5b72de0a05936b094db5d24304/comments/87919ac6e79521732b0ec65fa7f79853/description b/7da3bd5b72de0a05936b094db5d24304/comments/87919ac6e79521732b0ec65fa7f79853/description new file mode 100644 index 0000000..18b29d3 --- /dev/null +++ b/7da3bd5b72de0a05936b094db5d24304/comments/87919ac6e79521732b0ec65fa7f79853/description @@ -0,0 +1,2 @@ +I think this will be easy. We'll teach `ent edit ID` to search not only +issue IDs but also comment IDs, then reuse the existing edit code. From cae36d2da5d129884701a5a167e9adf58412617d Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 10:30:38 -0600 Subject: [PATCH 107/489] update 'description' in issue 7d2d236668872cf11f167ac0462f8751 --- 7d2d236668872cf11f167ac0462f8751/description | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 7d2d236668872cf11f167ac0462f8751/description diff --git a/7d2d236668872cf11f167ac0462f8751/description b/7d2d236668872cf11f167ac0462f8751/description new file mode 100644 index 0000000..488a279 --- /dev/null +++ b/7d2d236668872cf11f167ac0462f8751/description @@ -0,0 +1,13 @@ +add `ent tag ISSUE_ID TAG` + +Tags are short text strings without white space. + +The Issue directory will gain a file named `tags`, containing the sorted +list of tags. + +The `ent list` output will add a bit to the end of each issue displayed, +something like "[tag1, tag2]", or nothing if the issue has no tags +(like how we don't show assignee if there is none). + +As part of this issue, the `ent list FILTER` will gain a new filter +chunk type like "tags=v2.31,ux-papercut". From d77abed936f2e2fd2381692b8180552d963f63ad Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 11:02:22 -0600 Subject: [PATCH 108/489] update 'description' in issue db3eca1d2312bae8e62d2a237a184aa9 --- .../description | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 f57708cdac0607194ba61824e857b37c/comments/db3eca1d2312bae8e62d2a237a184aa9/description diff --git a/f57708cdac0607194ba61824e857b37c/comments/db3eca1d2312bae8e62d2a237a184aa9/description b/f57708cdac0607194ba61824e857b37c/comments/db3eca1d2312bae8e62d2a237a184aa9/description new file mode 100644 index 0000000..55f1a94 --- /dev/null +++ b/f57708cdac0607194ba61824e857b37c/comments/db3eca1d2312bae8e62d2a237a184aa9/description @@ -0,0 +1,36 @@ +One possible way to determine how much of the UUID we need to keep +for uniqueness: + +* Read the issue database into memory + +* Build a tree of all the issue ids: + + * Define a Node type for a tree datastructure as: + enum Node { + Empty, + IssueId(String), + SubNodes([&Node; 256]), + } + + * Create an empty root node = Node::Empty + + * For each issue: + issue_id_bytes = issue_id.iter() + + issue_id_byte = issue_id_bytes.next() + current_node = root + + while current_node == SubNodes: + current_node = current_node[issue_id_byte] + issue_id_byte = issue_id_bytes.next() + + if current_node == Empty: + current_node = Node::IssueId(issue_id) + else: # we know current_node == IssueId(original_issue_id) + replace current_node with a SubNodes initialized to [Node::Empty; 256] + current_node[original_issue_id[depth]] = Node::IssueId(original_issue_id) + recurse trying again to insert issue_id + +* Walk the entire tree, keeping track of the depth of each IssueId node (ie the number of SubNodes nodes above it) + +* The largest depth is the number of bytes of the issue ids neede to ensure uniqueness From 392bcda86e8e17b634727257b0fbe8ae3c02786c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 11:03:14 -0600 Subject: [PATCH 109/489] update 'state' in issue 9e69a30ad6965d7488514584c97ac63c --- 9e69a30ad6965d7488514584c97ac63c/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9e69a30ad6965d7488514584c97ac63c/state b/9e69a30ad6965d7488514584c97ac63c/state index 505c028..348ebd9 100644 --- a/9e69a30ad6965d7488514584c97ac63c/state +++ b/9e69a30ad6965d7488514584c97ac63c/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From c50de40d746de4e983a46cb0e25944ace0c0a1ca Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 11:03:45 -0600 Subject: [PATCH 110/489] update 'state' in issue 75cefad80aacbf23fc7b9c24a75aa236 --- 75cefad80aacbf23fc7b9c24a75aa236/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/75cefad80aacbf23fc7b9c24a75aa236/state b/75cefad80aacbf23fc7b9c24a75aa236/state index 505c028..348ebd9 100644 --- a/75cefad80aacbf23fc7b9c24a75aa236/state +++ b/75cefad80aacbf23fc7b9c24a75aa236/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From d387aa87b8572d55f8e6b1a561d65b78fd53269c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 11:05:04 -0600 Subject: [PATCH 111/489] update 'description' in issue 7db06053e24489963aec82a4084302de --- .../comments/7db06053e24489963aec82a4084302de/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 8edf884dbde5828a30a4dccad503f28a/comments/7db06053e24489963aec82a4084302de/description diff --git a/8edf884dbde5828a30a4dccad503f28a/comments/7db06053e24489963aec82a4084302de/description b/8edf884dbde5828a30a4dccad503f28a/comments/7db06053e24489963aec82a4084302de/description new file mode 100644 index 0000000..a2e859c --- /dev/null +++ b/8edf884dbde5828a30a4dccad503f28a/comments/7db06053e24489963aec82a4084302de/description @@ -0,0 +1 @@ +This is a duplicate of issue e089400e8a9e11fe9bf10d50b2f889d7 From 88b5caa17e71b5bbeb59235d9f99877d967b24ac Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 11:06:02 -0600 Subject: [PATCH 112/489] update 'assignee' in issue a26da230276d317e85f9fcca41c19d2e --- a26da230276d317e85f9fcca41c19d2e/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 a26da230276d317e85f9fcca41c19d2e/assignee diff --git a/a26da230276d317e85f9fcca41c19d2e/assignee b/a26da230276d317e85f9fcca41c19d2e/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/a26da230276d317e85f9fcca41c19d2e/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From 4649d2889da7cd2e1efd0aadfd339a371cf3ca4b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 11:06:17 -0600 Subject: [PATCH 113/489] update 'state' in issue a26da230276d317e85f9fcca41c19d2e --- a26da230276d317e85f9fcca41c19d2e/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 a26da230276d317e85f9fcca41c19d2e/state diff --git a/a26da230276d317e85f9fcca41c19d2e/state b/a26da230276d317e85f9fcca41c19d2e/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/a26da230276d317e85f9fcca41c19d2e/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From f545ae97f1ed329d1e1c5efc0c895fad25a6fa01 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 11:07:49 -0600 Subject: [PATCH 114/489] update 'assignee' in issue da435e5e298b28dc223f9dcfe62a9140 --- da435e5e298b28dc223f9dcfe62a9140/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 da435e5e298b28dc223f9dcfe62a9140/assignee diff --git a/da435e5e298b28dc223f9dcfe62a9140/assignee b/da435e5e298b28dc223f9dcfe62a9140/assignee new file mode 100644 index 0000000..2140339 --- /dev/null +++ b/da435e5e298b28dc223f9dcfe62a9140/assignee @@ -0,0 +1 @@ +lex \ No newline at end of file From da35631f2d3fd84a325de2b7d654ce0bd109c161 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 9 Jul 2025 12:51:39 -0600 Subject: [PATCH 115/489] update right-speech-bubble to use left-speech-bubble right-speech-bubble is not part of the recommended unicode emoji presentation set, but left-speech-bubble is and therefore, it's more commonly supported --- src/bin/ent/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 2892d6a..6fb30c9 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -120,7 +120,7 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( let issue = issues.issues.get(*uuid).unwrap(); let comments = match issue.comments.len() { 0 => String::from(" "), - n => format!("🗩 {}", n), + n => format!("🗨️ {}", n), }; let assignee = match &issue.assignee { Some(assignee) => format!(" (👉 {})", assignee), From 89b9eb32939356aaa023c58c893a2abcdb61f098 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 12:55:39 -0600 Subject: [PATCH 116/489] update 'description' in issue bed47b2be016cc41eb43ef6d0acf1f8f --- bed47b2be016cc41eb43ef6d0acf1f8f/description | 22 ++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/bed47b2be016cc41eb43ef6d0acf1f8f/description b/bed47b2be016cc41eb43ef6d0acf1f8f/description index 6b7ac88..18967cd 100644 --- a/bed47b2be016cc41eb43ef6d0acf1f8f/description +++ b/bed47b2be016cc41eb43ef6d0acf1f8f/description @@ -1,6 +1,20 @@ -add some way to un-assign an issue +make issue assignment more flexible -This will probably involve removing the `assignee` file from the issue -directory, which will mean teaching the git code about removed files. +I think there are two things i want that don't currently work: +* add some way to un-assign a person from an issue +* support multiple people assigned to the same issue -Or maybe write the empty string to that file? +Maybe the `assignee` field of the Issue struct should be a Vec +instead of String? And the `assignee` file of the issue directory should +be a list, one assignee per line? + +Possible new CLI ui: + +* `ent assign ISSUE PERSON[,PERSON...]` replaces the `assignee` list + with the new user-specified list. +* `ent assign ISSUE +PERSON` adds PERSON to the list +* `ent assign ISSUE -PERSON` removes the PERSON from the list + +Also support removing the `assignee` file from the issue directory if the +list is empty. Or maybe allow the file to exist but with 0 bytes in it? +Probably either one is fine. From 9abfea06cc0620ab4d41f573181d5d828dd47573 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 12:58:30 -0600 Subject: [PATCH 117/489] update 'description' in issue 5fe71e27727f4243dc997e63b9a02971 --- 5fe71e27727f4243dc997e63b9a02971/description | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 5fe71e27727f4243dc997e63b9a02971/description diff --git a/5fe71e27727f4243dc997e63b9a02971/description b/5fe71e27727f4243dc997e63b9a02971/description new file mode 100644 index 0000000..9f741d2 --- /dev/null +++ b/5fe71e27727f4243dc997e63b9a02971/description @@ -0,0 +1,7 @@ +add a way to see how an issue has changed over time + +Maybe `ent log ISSUE` (and `ent log COMMENT`)? + +This could be a thinly veiled wrapper around: + +`git log entomologist-data -- ISSUE` From 01df39f9b17b47c89667f16d0edbe1a601647b51 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 9 Jul 2025 12:58:32 -0600 Subject: [PATCH 118/489] update 'assignee' in issue 1ebdee0502937bf934bb0d72256dbdd1 --- 1ebdee0502937bf934bb0d72256dbdd1/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 1ebdee0502937bf934bb0d72256dbdd1/assignee diff --git a/1ebdee0502937bf934bb0d72256dbdd1/assignee b/1ebdee0502937bf934bb0d72256dbdd1/assignee new file mode 100644 index 0000000..680727a --- /dev/null +++ b/1ebdee0502937bf934bb0d72256dbdd1/assignee @@ -0,0 +1 @@ +03 \ No newline at end of file From 9d1043acbec5ac50cbc9aff644ec25fe1c2e376d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 9 Jul 2025 12:59:50 -0600 Subject: [PATCH 119/489] update 'state' in issue 093e87e8049b93bfa2d8fcd544cae75f --- 093e87e8049b93bfa2d8fcd544cae75f/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 093e87e8049b93bfa2d8fcd544cae75f/state diff --git a/093e87e8049b93bfa2d8fcd544cae75f/state b/093e87e8049b93bfa2d8fcd544cae75f/state new file mode 100644 index 0000000..348ebd9 --- /dev/null +++ b/093e87e8049b93bfa2d8fcd544cae75f/state @@ -0,0 +1 @@ +done \ No newline at end of file From 8cea4cfd0ff265def31e5e3864d21d0e88373a45 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 13:02:40 -0600 Subject: [PATCH 120/489] update 'description' in issue fd81241f795333b64e7911cfb1b57c8f --- fd81241f795333b64e7911cfb1b57c8f/description | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fd81241f795333b64e7911cfb1b57c8f/description b/fd81241f795333b64e7911cfb1b57c8f/description index fdb0c29..1a26aee 100644 --- a/fd81241f795333b64e7911cfb1b57c8f/description +++ b/fd81241f795333b64e7911cfb1b57c8f/description @@ -1 +1,8 @@ commit messages in the `entomologist-data` branch could be better + +It'd be nice to be able to look at the `git log --oneline` of an issue dir and see: +* creating the issue +* editing the issue +* changing state or assignee +* adding a comment +* editing a comment From c8ba45961f1f9dd3cee8102bd71d4d272ab6b15b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 13:41:14 -0600 Subject: [PATCH 121/489] update 'description' in issue 08f0d7ee7842c439382816d21ec1dea2 --- 08f0d7ee7842c439382816d21ec1dea2/description | 63 ++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 08f0d7ee7842c439382816d21ec1dea2/description diff --git a/08f0d7ee7842c439382816d21ec1dea2/description b/08f0d7ee7842c439382816d21ec1dea2/description new file mode 100644 index 0000000..a6bd3e8 --- /dev/null +++ b/08f0d7ee7842c439382816d21ec1dea2/description @@ -0,0 +1,63 @@ +figure out how to make useful reports + +In task-warrior we had a pair of custom reports, one to list things we +did last week and another to list things we were planning to do this +coming week. + +Docs here: +* custom reports: +* end date (brief): + + +# Done last week + +> report.weekly.prev.description='Tasks Completed Last Week' +> report.weekly.prev.columns=uuid,description.desc,project,assignee,end +> report.weekly.prev.sort=end+ +> report.weekly.prev.filter=+COMPLETED and -hidden and (end.after=sow - 1 week) and (end.by=eow - 1 week) + +The "done last week" report filters on a column called `end`, which is +a datetime made up by tw indicating when the ticket became COMPLETED. + + +# To do this week + +> report.selected.external.description='Tasks Selected for This Week' +> report.selected.external.columns=project,description.desc,assignee,due +> report.selected.external.sort=assignee+/,urgency- +> report.selected.external.filter=-COMPLETED and -internal and -hidden and (due.by=eow or +selected or +interrupt) + +The "to do this week" report looks for the tags "selected" or "interrupt", +or a due date this week. + + +# What is to be done + +## Done last week + +I can imagine finding the issues with state=done, running `git log` +on their state file, finding the datetime of the transition to done, +and selecting the ones where that datetime is in a particular time window. + +Not sure how to express to ent that that is what i want though. + +Maybe we can use git tags in the entomologist-data branch somehow? +`git log` between tags and look for "state: _ -> done". But how to +position the tags on the commits i want? Seems cumbersome. + + +## To do this week + +entomologist doesn't have due dates yet, but we should probably add that. + +entomologist doesn't have tags yet (there's an open ticket for it), +but once we have that it'll be easy to list "tags=selected,interrupt". +Or maybe we should just use "state=inprogress"? + + +## Misc thoughts + +It's nice that we can add whatever files we want to the +`entomologist-data` branch. We could add a directory of report +definitions, and have them be shared across the project, and not have +to type them out every time. From ccf4ae4afb6a1494f5bbe17b3f8b95b2e8d92b34 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 13:47:58 -0600 Subject: [PATCH 122/489] update 'description' in issue 54f0eb67b05aa10763c86869ce840f33 --- 54f0eb67b05aa10763c86869ce840f33/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 54f0eb67b05aa10763c86869ce840f33/description diff --git a/54f0eb67b05aa10763c86869ce840f33/description b/54f0eb67b05aa10763c86869ce840f33/description new file mode 100644 index 0000000..e54c1dc --- /dev/null +++ b/54f0eb67b05aa10763c86869ce840f33/description @@ -0,0 +1 @@ +`ent sync` should report what changes got fetched & pushed From 54d3ee56cffc949a9ede63146abddb261029f17a Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 15:27:51 -0600 Subject: [PATCH 123/489] update 'description' in issue c9b5bd61e7e103c8ab3e7920fcbc67e1 --- c9b5bd61e7e103c8ab3e7920fcbc67e1/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 c9b5bd61e7e103c8ab3e7920fcbc67e1/description diff --git a/c9b5bd61e7e103c8ab3e7920fcbc67e1/description b/c9b5bd61e7e103c8ab3e7920fcbc67e1/description new file mode 100644 index 0000000..db8679b --- /dev/null +++ b/c9b5bd61e7e103c8ab3e7920fcbc67e1/description @@ -0,0 +1,3 @@ +sometimes the output should go to a pager + +`ent show` definitely should go in a pager. `ent list` maybe not? From c363841c1d0c8df8d367c32b378f573367643fa5 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 15:28:27 -0600 Subject: [PATCH 124/489] update 'description' in issue 89a4c085d5202f0be7d9c6d263c4c4b8 --- 89a4c085d5202f0be7d9c6d263c4c4b8/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 89a4c085d5202f0be7d9c6d263c4c4b8/description diff --git a/89a4c085d5202f0be7d9c6d263c4c4b8/description b/89a4c085d5202f0be7d9c6d263c4c4b8/description new file mode 100644 index 0000000..8089d24 --- /dev/null +++ b/89a4c085d5202f0be7d9c6d263c4c4b8/description @@ -0,0 +1,3 @@ +manage whitespace in issues and comments + +Strip leading and trailing whitespace when loading and saving descriptions. From a46c32c9ff7aeebe0b440933af310e40cd3fd9df Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 15:29:59 -0600 Subject: [PATCH 125/489] update 'description' in issue 4527a79c3802573d72f513c2f9cc9eda --- .../4527a79c3802573d72f513c2f9cc9eda/description | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 ae56208b6db64ccd9da31ce2bb1aad64/comments/4527a79c3802573d72f513c2f9cc9eda/description diff --git a/ae56208b6db64ccd9da31ce2bb1aad64/comments/4527a79c3802573d72f513c2f9cc9eda/description b/ae56208b6db64ccd9da31ce2bb1aad64/comments/4527a79c3802573d72f513c2f9cc9eda/description new file mode 100644 index 0000000..e00a7f8 --- /dev/null +++ b/ae56208b6db64ccd9da31ce2bb1aad64/comments/4527a79c3802573d72f513c2f9cc9eda/description @@ -0,0 +1,15 @@ +``` +$ ent sync +Enter passphrase for key '/home/seb/.ssh/id_ed25519': +^C + +$ ent sync +stdout: +stderr: Preparing worktree (checking out 'entomologist-data') +fatal: 'entomologist-data' is already used by worktree at '/tmp/.tmpgKK2gw' + +Error: Oops, something went wrong + +$ rm -rf /tmp/.tmpgKK2gw/ +$ git worktree prune +``` From 5203a81dd7b367337f617367f89c37c442c3a8fc Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 15:48:09 -0600 Subject: [PATCH 126/489] update 'description' in issue 9502937b139b797f94eb422651417757 --- 9502937b139b797f94eb422651417757/description | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 9502937b139b797f94eb422651417757/description diff --git a/9502937b139b797f94eb422651417757/description b/9502937b139b797f94eb422651417757/description new file mode 100644 index 0000000..5dbd141 --- /dev/null +++ b/9502937b139b797f94eb422651417757/description @@ -0,0 +1,13 @@ +visually distinguish issues and comments from each other + +`git log` does it well: + +* The first line of each commit (that says `commit SHA`) is in a + different color + +* The commit message is indented, making it visually distinctive from + the commit header which is not indented + +* There's a blank line after each commit, before the next one starts + +Something similar in `ent show` would be good. From 59b27e9d60ea0e668ad845d2237353aa3fe31702 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 20:29:04 -0600 Subject: [PATCH 127/489] update 'assignee' in issue a5ac277614ea4d13f78031abb25ea7d6 --- a5ac277614ea4d13f78031abb25ea7d6/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 a5ac277614ea4d13f78031abb25ea7d6/assignee diff --git a/a5ac277614ea4d13f78031abb25ea7d6/assignee b/a5ac277614ea4d13f78031abb25ea7d6/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/a5ac277614ea4d13f78031abb25ea7d6/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From 3233d70cc78d6bab83921d4cb04dc71a60ed97e6 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 20:29:09 -0600 Subject: [PATCH 128/489] update 'state' in issue a5ac277614ea4d13f78031abb25ea7d6 --- a5ac277614ea4d13f78031abb25ea7d6/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 a5ac277614ea4d13f78031abb25ea7d6/state diff --git a/a5ac277614ea4d13f78031abb25ea7d6/state b/a5ac277614ea4d13f78031abb25ea7d6/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/a5ac277614ea4d13f78031abb25ea7d6/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 2f8022b8b74a7b5b93fdc18f6c047444171e882d Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 20:32:01 -0600 Subject: [PATCH 129/489] update 'description' in issue 047869e6714d6d31cd3dd55de47b0871 --- .../comments/047869e6714d6d31cd3dd55de47b0871/description | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 a97c817024233be0e34536dfb1323070/comments/047869e6714d6d31cd3dd55de47b0871/description diff --git a/a97c817024233be0e34536dfb1323070/comments/047869e6714d6d31cd3dd55de47b0871/description b/a97c817024233be0e34536dfb1323070/comments/047869e6714d6d31cd3dd55de47b0871/description new file mode 100644 index 0000000..d4468c9 --- /dev/null +++ b/a97c817024233be0e34536dfb1323070/comments/047869e6714d6d31cd3dd55de47b0871/description @@ -0,0 +1,2 @@ +This will probably require `struct Issue` to incorporate the issue uuid, +like `struct Comment` does. From ca353352f8dced335506ffb4bd839213b64afefb Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:03:49 -0600 Subject: [PATCH 130/489] git::Worktree::drop() now force-drops the worktree This avoids leaving prunable worktrees around if we dirtied the worktree and didn't commit. This can happen in the following situation: 1. User runs `ent new`. 2. ent creates a new directory for the issue. 3. ent opens an editor to let the user type in the description of the new issue. The editor saves to `ISSUE/description`. 4. User changes their mind and no longer wants to make a new issue, so they save an empty buffer and exit the editor. 5. ent sees that the file is empty, and returns an error from Issue::edit_description(). 6. ent propagates the error up through program exit, and eventually the git::Worktree struct is dropped. Since the worktree is dirty (it has the new issue dir with an empty description file in it), `git worktree remove` fails. But `git worktree remove --force` works! --- src/git.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/git.rs b/src/git.rs index 6018392..d83389e 100644 --- a/src/git.rs +++ b/src/git.rs @@ -20,9 +20,24 @@ pub struct Worktree { impl Drop for Worktree { fn drop(&mut self) { - let _result = std::process::Command::new("git") - .args(["worktree", "remove", &self.path.path().to_string_lossy()]) + let result = std::process::Command::new("git") + .args([ + "worktree", + "remove", + "--force", + &self.path.path().to_string_lossy(), + ]) .output(); + match result { + Err(e) => { + println!("failed to run git: {:#?}", e); + } + Ok(result) => { + if !result.status.success() { + println!("failed to remove git worktree: {:#?}", result); + } + } + } } } From 1509c42734e360d32c235f8e54dda5835f52e82b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:20:37 -0600 Subject: [PATCH 131/489] add git::worktree_is_dirty() This returns Ok(true) if the worktree has any modified files (staged or unstaged), or any added (staged) files. Ok(false) if not. Ignores untracked files. --- src/git.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/git.rs b/src/git.rs index d83389e..54b5735 100644 --- a/src/git.rs +++ b/src/git.rs @@ -106,6 +106,17 @@ pub fn git_branch_exists(branch: &str) -> Result { return Ok(result.status.success()); } +pub fn worktree_is_dirty(dir: &str) -> Result { + // `git status --porcelain` prints a terse list of files added or + // modified (both staged and not), and new untracked files. So if + // says *anything at all* it means the worktree is dirty. + let result = std::process::Command::new("git") + .args(["status", "--porcelain", "--untracked-files=no"]) + .current_dir(dir) + .output()?; + return Ok(result.stdout.len() > 0); +} + pub fn git_commit_file(file: &std::path::Path) -> Result<(), GitError> { let mut git_dir = std::path::PathBuf::from(file); git_dir.pop(); From ac72251e0e5c9fb12116eb1f71379f02d1eb22e2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:23:02 -0600 Subject: [PATCH 132/489] add git::restore_file() This restores a file from the index to the worktree. --- src/git.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/git.rs b/src/git.rs index 54b5735..17a00bc 100644 --- a/src/git.rs +++ b/src/git.rs @@ -117,6 +117,19 @@ pub fn worktree_is_dirty(dir: &str) -> Result { return Ok(result.stdout.len() > 0); } +pub fn restore_file(file: &std::path::Path) -> Result<(), GitError> { + let result = std::process::Command::new("git") + .args(["restore", &file.to_string_lossy()]) + .current_dir(file.parent().unwrap()) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + return Ok(()); +} + pub fn git_commit_file(file: &std::path::Path) -> Result<(), GitError> { let mut git_dir = std::path::PathBuf::from(file); git_dir.pop(); From 16de030b8e64b2b3705995d8ad39e8bb13cd99fe Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 21:16:28 -0600 Subject: [PATCH 133/489] add git::add_file() --- src/git.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/git.rs b/src/git.rs index 17a00bc..087b13c 100644 --- a/src/git.rs +++ b/src/git.rs @@ -117,6 +117,19 @@ pub fn worktree_is_dirty(dir: &str) -> Result { return Ok(result.stdout.len() > 0); } +pub fn add_file(file: &std::path::Path) -> Result<(), GitError> { + let result = std::process::Command::new("git") + .args(["add", &file.to_string_lossy()]) + .current_dir(file.parent().unwrap()) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + return Ok(()); +} + pub fn restore_file(file: &std::path::Path) -> Result<(), GitError> { let result = std::process::Command::new("git") .args(["restore", &file.to_string_lossy()]) From bfdf6178f4dca2d492a9c514f8f7953d89bc1e1b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 21:16:37 -0600 Subject: [PATCH 134/489] add git::commit() --- src/git.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/git.rs b/src/git.rs index 087b13c..3374542 100644 --- a/src/git.rs +++ b/src/git.rs @@ -143,6 +143,19 @@ pub fn restore_file(file: &std::path::Path) -> Result<(), GitError> { return Ok(()); } +pub fn commit(dir: &std::path::Path, msg: &str) -> Result<(), GitError> { + let result = std::process::Command::new("git") + .args(["commit", "-m", msg]) + .current_dir(dir) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + Ok(()) +} + pub fn git_commit_file(file: &std::path::Path) -> Result<(), GitError> { let mut git_dir = std::path::PathBuf::from(file); git_dir.pop(); From 211bf92dde1a07dbf15d89285a51c8d3d533d83c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:21:31 -0600 Subject: [PATCH 135/489] Issue: handle empty description from user This fixes issue a26da230276d317e85f9fcca41c19d2e. --- src/issue.rs | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/issue.rs b/src/issue.rs index 68cb8f9..7201871 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -47,6 +47,8 @@ pub enum IssueError { GitError(#[from] crate::git::GitError), #[error("Failed to run editor")] EditorError, + #[error("supplied description is empty")] + EmptyDescription, } impl FromStr for State { @@ -195,6 +197,9 @@ impl Issue { } pub fn set_description(&mut self, description: &str) -> Result<(), IssueError> { + if description.len() == 0 { + return Err(IssueError::EmptyDescription); + } self.description = String::from(description); let mut description_filename = std::path::PathBuf::from(&self.dir); description_filename.push("description"); @@ -214,6 +219,7 @@ impl Issue { pub fn edit_description(&mut self) -> Result<(), IssueError> { let mut description_filename = std::path::PathBuf::from(&self.dir); description_filename.push("description"); + let exists = description_filename.exists(); let result = std::process::Command::new("vi") .arg(&description_filename.as_mut_os_str()) .spawn()? @@ -223,8 +229,31 @@ impl Issue { println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); return Err(IssueError::EditorError); } - crate::git::git_commit_file(&description_filename)?; - self.read_description()?; + if description_filename.exists() && description_filename.metadata()?.len() > 0 { + crate::git::add_file(&description_filename)?; + } else { + // User saved an empty file, which means they changed their + // mind and no longer want to edit the description. + if exists { + crate::git::restore_file(&description_filename)?; + } + return Err(IssueError::EmptyDescription); + } + if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { + crate::git::commit( + &description_filename.parent().unwrap(), + &format!( + "new description for issue {}", + description_filename + .parent() + .unwrap() + .file_name() + .unwrap() + .to_string_lossy() + ), + )?; + self.read_description()?; + } Ok(()) } From fc658009f5fd51a8987cae057d2690b2278b833c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:34:55 -0600 Subject: [PATCH 136/489] Comment: handle empty description --- src/comment.rs | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/comment.rs b/src/comment.rs index d6ed66e..e6c95fd 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -22,6 +22,8 @@ pub enum CommentError { GitError(#[from] crate::git::GitError), #[error("Failed to run editor")] EditorError, + #[error("supplied description is empty")] + EmptyDescription, } impl Comment { @@ -60,6 +62,9 @@ impl Comment { } pub fn set_description(&mut self, description: &str) -> Result<(), CommentError> { + if description.len() == 0 { + return Err(CommentError::EmptyDescription); + } self.description = String::from(description); let mut description_filename = std::path::PathBuf::from(&self.dir); description_filename.push("description"); @@ -79,6 +84,7 @@ impl Comment { pub fn edit_description(&mut self) -> Result<(), CommentError> { let mut description_filename = std::path::PathBuf::from(&self.dir); description_filename.push("description"); + let exists = description_filename.exists(); let result = std::process::Command::new("vi") .arg(&description_filename.as_mut_os_str()) .spawn()? @@ -88,8 +94,31 @@ impl Comment { println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); return Err(CommentError::EditorError); } - crate::git::git_commit_file(&description_filename)?; - self.read_description()?; + if description_filename.exists() && description_filename.metadata()?.len() > 0 { + crate::git::add_file(&description_filename)?; + } else { + // User saved an empty file, which means they changed their + // mind and no longer want to edit the description. + if exists { + crate::git::restore_file(&description_filename)?; + } + return Err(CommentError::EmptyDescription); + } + if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { + crate::git::commit( + &description_filename.parent().unwrap(), + &format!( + "new description for comment {}", + description_filename + .parent() + .unwrap() + .file_name() + .unwrap() + .to_string_lossy() + ), + )?; + self.read_description()?; + } Ok(()) } } From a199fbc7f71dc0565a6c811735981ec8fc73d829 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:09:50 -0600 Subject: [PATCH 137/489] handle aborts in `ent edit ISSUE` The user saving an empty description file is a normal user-initiated abort, not an error. --- src/bin/ent/main.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 6fb30c9..fa18b2a 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -150,9 +150,16 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( let mut issues = entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; match issues.get_mut_issue(issue_id) { - Some(issue) => { - issue.edit_description()?; - } + Some(issue) => match issue.edit_description() { + Err(entomologist::issue::IssueError::EmptyDescription) => { + println!("aborted issue edit"); + return Ok(()); + } + Err(e) => { + return Err(e.into()); + } + Ok(()) => (), + }, None => { return Err(anyhow::anyhow!("issue {} not found", issue_id)); } From acf539c683a6545bd01f866bed0c82fd0fa33efb Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:11:27 -0600 Subject: [PATCH 138/489] handle user abort in `ent comment` The user saving an empty description is a normal user-initiated abort, not an error. --- src/bin/ent/main.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index fa18b2a..893534b 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -234,12 +234,20 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( return Err(anyhow::anyhow!("issue {} not found", issue_id)); }; let mut comment = issue.new_comment()?; - match description { - Some(description) => { - comment.set_description(description)?; + let r = match description { + Some(description) => comment.set_description(description), + None => comment.edit_description(), + }; + match r { + Err(entomologist::comment::CommentError::EmptyDescription) => { + println!("aborted new comment"); + return Ok(()); } - None => { - comment.edit_description()?; + Err(e) => { + return Err(e.into()); + } + Ok(()) => { + println!("created new comment {}", &comment.uuid); } } } From e09e4b9cb72608c4eb7f83f7bae433d1c0180b3c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:12:58 -0600 Subject: [PATCH 139/489] simplify `ent new` --- src/bin/ent/main.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 893534b..8bc0817 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -132,18 +132,24 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } } - Commands::New { - description: Some(description), - } => { + Commands::New { description } => { let mut issue = entomologist::issue::Issue::new(issues_dir)?; - issue.set_description(description)?; - println!("created new issue '{}'", issue.title()); - } - - Commands::New { description: None } => { - let mut issue = entomologist::issue::Issue::new(issues_dir)?; - issue.edit_description()?; - println!("created new issue '{}'", issue.title()); + let r = match description { + Some(description) => issue.set_description(description), + None => issue.edit_description(), + }; + match r { + Err(entomologist::issue::IssueError::EmptyDescription) => { + println!("no new issue created"); + return Ok(()); + } + Err(e) => { + return Err(e.into()); + } + Ok(()) => { + println!("created new issue '{}'", issue.title()); + } + } } Commands::Edit { issue_id } => { From d672ac14f654803af8ded3fd422aa4e53896c791 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:40:51 -0600 Subject: [PATCH 140/489] new description for comment f432474d9d62b6a3f23026b58dbc9045 --- .../comments/f432474d9d62b6a3f23026b58dbc9045/description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 ae56208b6db64ccd9da31ce2bb1aad64/comments/f432474d9d62b6a3f23026b58dbc9045/description diff --git a/ae56208b6db64ccd9da31ce2bb1aad64/comments/f432474d9d62b6a3f23026b58dbc9045/description b/ae56208b6db64ccd9da31ce2bb1aad64/comments/f432474d9d62b6a3f23026b58dbc9045/description new file mode 100644 index 0000000..eca3473 --- /dev/null +++ b/ae56208b6db64ccd9da31ce2bb1aad64/comments/f432474d9d62b6a3f23026b58dbc9045/description @@ -0,0 +1,4 @@ +There was a bug in entomologist::git::Worktree::drop() which would make +it fail to clean up the worktree if it was dirty. + +This was fixed in commit ca353352f8dced335506ffb4bd839213b64afefb. From 561b9cf35cef374e32c0d6b6102ec2921f8e3f97 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:41:07 -0600 Subject: [PATCH 141/489] update 'assignee' in issue ae56208b6db64ccd9da31ce2bb1aad64 --- ae56208b6db64ccd9da31ce2bb1aad64/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 ae56208b6db64ccd9da31ce2bb1aad64/assignee diff --git a/ae56208b6db64ccd9da31ce2bb1aad64/assignee b/ae56208b6db64ccd9da31ce2bb1aad64/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/ae56208b6db64ccd9da31ce2bb1aad64/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From 0b6a6957ef323f214c6b74a212fd1849b6066280 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:41:11 -0600 Subject: [PATCH 142/489] update 'state' in issue ae56208b6db64ccd9da31ce2bb1aad64 --- ae56208b6db64ccd9da31ce2bb1aad64/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 ae56208b6db64ccd9da31ce2bb1aad64/state diff --git a/ae56208b6db64ccd9da31ce2bb1aad64/state b/ae56208b6db64ccd9da31ce2bb1aad64/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/ae56208b6db64ccd9da31ce2bb1aad64/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 1633abb44b879e1db15a97f39ba8b9a69008a7b2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:50:12 -0600 Subject: [PATCH 143/489] new description for issue bd71d3896b8fa3e19cde4b5de9d9ac78 --- bd71d3896b8fa3e19cde4b5de9d9ac78/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 bd71d3896b8fa3e19cde4b5de9d9ac78/description diff --git a/bd71d3896b8fa3e19cde4b5de9d9ac78/description b/bd71d3896b8fa3e19cde4b5de9d9ac78/description new file mode 100644 index 0000000..aa451d9 --- /dev/null +++ b/bd71d3896b8fa3e19cde4b5de9d9ac78/description @@ -0,0 +1 @@ +figure out how to do tab completion, including for issue ids From dc6c98a0723cddd6a2a14b5824fefd34d86415c8 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:55:24 -0600 Subject: [PATCH 144/489] new description for comment 2419a8f74876a6100e0aa9de80c387e6 --- .../comments/2419a8f74876a6100e0aa9de80c387e6/description | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 ae56208b6db64ccd9da31ce2bb1aad64/comments/2419a8f74876a6100e0aa9de80c387e6/description diff --git a/ae56208b6db64ccd9da31ce2bb1aad64/comments/2419a8f74876a6100e0aa9de80c387e6/description b/ae56208b6db64ccd9da31ce2bb1aad64/comments/2419a8f74876a6100e0aa9de80c387e6/description new file mode 100644 index 0000000..3eaaaf3 --- /dev/null +++ b/ae56208b6db64ccd9da31ce2bb1aad64/comments/2419a8f74876a6100e0aa9de80c387e6/description @@ -0,0 +1,6 @@ +While many "stray worktree" bugs got fixed by commit +ca353352f8dced335506ffb4bd839213b64afefb, the case of Control-C still +leaves a stray worktree behind because it doesn't shut down the process +cleanly, so `Worktree::drop()` doesn't get run. + +Not sure what the best way to fix this is. From 34af7c981f22e61cac3b2e066c81f6bf746ae37b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:57:08 -0600 Subject: [PATCH 145/489] new description for comment 02ad89e0f5411d684136af9b98196b87 --- .../comments/02ad89e0f5411d684136af9b98196b87/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 eee4a129dacac9ddff2e50580e822cbf/comments/02ad89e0f5411d684136af9b98196b87/description diff --git a/eee4a129dacac9ddff2e50580e822cbf/comments/02ad89e0f5411d684136af9b98196b87/description b/eee4a129dacac9ddff2e50580e822cbf/comments/02ad89e0f5411d684136af9b98196b87/description new file mode 100644 index 0000000..053959e --- /dev/null +++ b/eee4a129dacac9ddff2e50580e822cbf/comments/02ad89e0f5411d684136af9b98196b87/description @@ -0,0 +1,3 @@ +I'm unable to reproduce this. Maybe this was a bug earlier but fixed now? + +If so let's move it to Done or WontDo. From 7ed8af43606aa73c112f69c849e31e31aa0d8056 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:59:15 -0600 Subject: [PATCH 146/489] update 'state' in issue ae56208b6db64ccd9da31ce2bb1aad64 --- ae56208b6db64ccd9da31ce2bb1aad64/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ae56208b6db64ccd9da31ce2bb1aad64/state b/ae56208b6db64ccd9da31ce2bb1aad64/state index 505c028..b6fe829 100644 --- a/ae56208b6db64ccd9da31ce2bb1aad64/state +++ b/ae56208b6db64ccd9da31ce2bb1aad64/state @@ -1 +1 @@ -inprogress \ No newline at end of file +backlog \ No newline at end of file From 7187a7958ffe0bd5ccea3116e71c6345e3fcbcd4 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:59:41 -0600 Subject: [PATCH 147/489] update 'state' in issue 7da3bd5b72de0a05936b094db5d24304 --- 7da3bd5b72de0a05936b094db5d24304/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 7da3bd5b72de0a05936b094db5d24304/state diff --git a/7da3bd5b72de0a05936b094db5d24304/state b/7da3bd5b72de0a05936b094db5d24304/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/7da3bd5b72de0a05936b094db5d24304/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 70c76e671092ce0067d402d56f4cd23f812413a1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:59:51 -0600 Subject: [PATCH 148/489] update 'state' in issue 1f85dfac686d5ea2417b2b07f7e1ff01 --- 1f85dfac686d5ea2417b2b07f7e1ff01/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 1f85dfac686d5ea2417b2b07f7e1ff01/state diff --git a/1f85dfac686d5ea2417b2b07f7e1ff01/state b/1f85dfac686d5ea2417b2b07f7e1ff01/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/1f85dfac686d5ea2417b2b07f7e1ff01/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 95544ec994c6ad7452c0830a9d8c94a026e94dc1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 22:59:59 -0600 Subject: [PATCH 149/489] update 'state' in issue b738f2842db428df1b4aad0192a7f36c --- b738f2842db428df1b4aad0192a7f36c/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 b738f2842db428df1b4aad0192a7f36c/state diff --git a/b738f2842db428df1b4aad0192a7f36c/state b/b738f2842db428df1b4aad0192a7f36c/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/b738f2842db428df1b4aad0192a7f36c/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From a5a57ea6e7235faeabf2df3161637542957f1f10 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:32 -0600 Subject: [PATCH 150/489] update 'state' in issue 1ebdee0502937bf934bb0d72256dbdd1 --- 1ebdee0502937bf934bb0d72256dbdd1/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 1ebdee0502937bf934bb0d72256dbdd1/state diff --git a/1ebdee0502937bf934bb0d72256dbdd1/state b/1ebdee0502937bf934bb0d72256dbdd1/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/1ebdee0502937bf934bb0d72256dbdd1/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 993dd5cecefb941210fb7fb40d1e226594a251c4 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:33 -0600 Subject: [PATCH 151/489] update 'state' in issue a97c817024233be0e34536dfb1323070 --- a97c817024233be0e34536dfb1323070/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 a97c817024233be0e34536dfb1323070/state diff --git a/a97c817024233be0e34536dfb1323070/state b/a97c817024233be0e34536dfb1323070/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/a97c817024233be0e34536dfb1323070/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From f77e9ce9f52550a1877b56aeb6d29d1670e71d73 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:33 -0600 Subject: [PATCH 152/489] update 'state' in issue eee4a129dacac9ddff2e50580e822cbf --- eee4a129dacac9ddff2e50580e822cbf/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 eee4a129dacac9ddff2e50580e822cbf/state diff --git a/eee4a129dacac9ddff2e50580e822cbf/state b/eee4a129dacac9ddff2e50580e822cbf/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/eee4a129dacac9ddff2e50580e822cbf/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From d1f30a92ce61128adafd9a9e3e060a71befe0464 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:34 -0600 Subject: [PATCH 153/489] update 'state' in issue 317ea8ccac1d414cde55771321bdec30 --- 317ea8ccac1d414cde55771321bdec30/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 317ea8ccac1d414cde55771321bdec30/state diff --git a/317ea8ccac1d414cde55771321bdec30/state b/317ea8ccac1d414cde55771321bdec30/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/317ea8ccac1d414cde55771321bdec30/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 6bb47d1ecc047e9292147b10f72e9711a7c6742d Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:35 -0600 Subject: [PATCH 154/489] update 'state' in issue fd81241f795333b64e7911cfb1b57c8f --- fd81241f795333b64e7911cfb1b57c8f/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 fd81241f795333b64e7911cfb1b57c8f/state diff --git a/fd81241f795333b64e7911cfb1b57c8f/state b/fd81241f795333b64e7911cfb1b57c8f/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/fd81241f795333b64e7911cfb1b57c8f/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 0aedf7330252a51956c871b29d8d66074ae6cd2b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:35 -0600 Subject: [PATCH 155/489] update 'state' in issue f57708cdac0607194ba61824e857b37c --- f57708cdac0607194ba61824e857b37c/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 f57708cdac0607194ba61824e857b37c/state diff --git a/f57708cdac0607194ba61824e857b37c/state b/f57708cdac0607194ba61824e857b37c/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/f57708cdac0607194ba61824e857b37c/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 5e37b0bbe111dedae83c0f8ab9be282e99301038 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:36 -0600 Subject: [PATCH 156/489] update 'state' in issue bed47b2be016cc41eb43ef6d0acf1f8f --- bed47b2be016cc41eb43ef6d0acf1f8f/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 bed47b2be016cc41eb43ef6d0acf1f8f/state diff --git a/bed47b2be016cc41eb43ef6d0acf1f8f/state b/bed47b2be016cc41eb43ef6d0acf1f8f/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/bed47b2be016cc41eb43ef6d0acf1f8f/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 2779d80b6d24a397c7711883c3a042142cbd61f1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:37 -0600 Subject: [PATCH 157/489] update 'state' in issue 7d2d236668872cf11f167ac0462f8751 --- 7d2d236668872cf11f167ac0462f8751/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 7d2d236668872cf11f167ac0462f8751/state diff --git a/7d2d236668872cf11f167ac0462f8751/state b/7d2d236668872cf11f167ac0462f8751/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/7d2d236668872cf11f167ac0462f8751/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From c83eaaee51c5cdc6270f58a202e3ddf179073872 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:37 -0600 Subject: [PATCH 158/489] update 'state' in issue 5fe71e27727f4243dc997e63b9a02971 --- 5fe71e27727f4243dc997e63b9a02971/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 5fe71e27727f4243dc997e63b9a02971/state diff --git a/5fe71e27727f4243dc997e63b9a02971/state b/5fe71e27727f4243dc997e63b9a02971/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/5fe71e27727f4243dc997e63b9a02971/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 8f1828c87235636898c5791faa55a032f3d281d3 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:38 -0600 Subject: [PATCH 159/489] update 'state' in issue 08f0d7ee7842c439382816d21ec1dea2 --- 08f0d7ee7842c439382816d21ec1dea2/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 08f0d7ee7842c439382816d21ec1dea2/state diff --git a/08f0d7ee7842c439382816d21ec1dea2/state b/08f0d7ee7842c439382816d21ec1dea2/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/08f0d7ee7842c439382816d21ec1dea2/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From ce427761069e562dfc90b04bda17b138e9699c2a Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:39 -0600 Subject: [PATCH 160/489] update 'state' in issue 54f0eb67b05aa10763c86869ce840f33 --- 54f0eb67b05aa10763c86869ce840f33/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 54f0eb67b05aa10763c86869ce840f33/state diff --git a/54f0eb67b05aa10763c86869ce840f33/state b/54f0eb67b05aa10763c86869ce840f33/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/54f0eb67b05aa10763c86869ce840f33/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From a6da4cdb5b8721a50520f243639cbec161335cfb Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:39 -0600 Subject: [PATCH 161/489] update 'state' in issue c9b5bd61e7e103c8ab3e7920fcbc67e1 --- c9b5bd61e7e103c8ab3e7920fcbc67e1/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 c9b5bd61e7e103c8ab3e7920fcbc67e1/state diff --git a/c9b5bd61e7e103c8ab3e7920fcbc67e1/state b/c9b5bd61e7e103c8ab3e7920fcbc67e1/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/c9b5bd61e7e103c8ab3e7920fcbc67e1/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From faf6a1aeacab9160fba76aa0a324199e25483a12 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:40 -0600 Subject: [PATCH 162/489] update 'state' in issue 89a4c085d5202f0be7d9c6d263c4c4b8 --- 89a4c085d5202f0be7d9c6d263c4c4b8/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 89a4c085d5202f0be7d9c6d263c4c4b8/state diff --git a/89a4c085d5202f0be7d9c6d263c4c4b8/state b/89a4c085d5202f0be7d9c6d263c4c4b8/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/89a4c085d5202f0be7d9c6d263c4c4b8/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 193131dd0ab432bfaf283552d6de9780c872f8de Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:41 -0600 Subject: [PATCH 163/489] update 'state' in issue 9502937b139b797f94eb422651417757 --- 9502937b139b797f94eb422651417757/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 9502937b139b797f94eb422651417757/state diff --git a/9502937b139b797f94eb422651417757/state b/9502937b139b797f94eb422651417757/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/9502937b139b797f94eb422651417757/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 0eb9e7efb38fd698319c27acdb68d58d5af9a37b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:00:41 -0600 Subject: [PATCH 164/489] update 'state' in issue bd71d3896b8fa3e19cde4b5de9d9ac78 --- bd71d3896b8fa3e19cde4b5de9d9ac78/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 bd71d3896b8fa3e19cde4b5de9d9ac78/state diff --git a/bd71d3896b8fa3e19cde4b5de9d9ac78/state b/bd71d3896b8fa3e19cde4b5de9d9ac78/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/bd71d3896b8fa3e19cde4b5de9d9ac78/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From a2413d21031629bbbc311bb7dc756e2ec4d92a5e Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:04:35 -0600 Subject: [PATCH 165/489] new description for comment 5ef6900a2353a7223a324e3bd2891bdf --- .../comments/5ef6900a2353a7223a324e3bd2891bdf/description | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 317ea8ccac1d414cde55771321bdec30/comments/5ef6900a2353a7223a324e3bd2891bdf/description diff --git a/317ea8ccac1d414cde55771321bdec30/comments/5ef6900a2353a7223a324e3bd2891bdf/description b/317ea8ccac1d414cde55771321bdec30/comments/5ef6900a2353a7223a324e3bd2891bdf/description new file mode 100644 index 0000000..2f3535e --- /dev/null +++ b/317ea8ccac1d414cde55771321bdec30/comments/5ef6900a2353a7223a324e3bd2891bdf/description @@ -0,0 +1,5 @@ +It's easy to make worktrees with detached heads: +``` +$ git worktree add --detach /tmp/ent1 entomologist-data +$ git worktree add --detach /tmp/ent2 entomologist-data +``` From 03de4eff8ac42029b2481dfaade24a9da67c8044 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:05:03 -0600 Subject: [PATCH 166/489] update 'assignee' in issue 317ea8ccac1d414cde55771321bdec30 --- 317ea8ccac1d414cde55771321bdec30/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 317ea8ccac1d414cde55771321bdec30/assignee diff --git a/317ea8ccac1d414cde55771321bdec30/assignee b/317ea8ccac1d414cde55771321bdec30/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/317ea8ccac1d414cde55771321bdec30/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From 73a0b7c29b02307ab1a9413394d772707930ef2d Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 9 Jul 2025 23:05:09 -0600 Subject: [PATCH 167/489] update 'state' in issue 317ea8ccac1d414cde55771321bdec30 --- 317ea8ccac1d414cde55771321bdec30/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/317ea8ccac1d414cde55771321bdec30/state b/317ea8ccac1d414cde55771321bdec30/state index b6fe829..505c028 100644 --- a/317ea8ccac1d414cde55771321bdec30/state +++ b/317ea8ccac1d414cde55771321bdec30/state @@ -1 +1 @@ -backlog \ No newline at end of file +inprogress \ No newline at end of file From 7d4b29ff3152bf74ef253f5b69b82959af472203 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 09:49:17 -0600 Subject: [PATCH 168/489] update 'state' in issue a26da230276d317e85f9fcca41c19d2e --- a26da230276d317e85f9fcca41c19d2e/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/a26da230276d317e85f9fcca41c19d2e/state b/a26da230276d317e85f9fcca41c19d2e/state index 505c028..348ebd9 100644 --- a/a26da230276d317e85f9fcca41c19d2e/state +++ b/a26da230276d317e85f9fcca41c19d2e/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From e9c1fd6fcdedb3a2cba55be870eec0df4c6d1f29 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 09:49:24 -0600 Subject: [PATCH 169/489] update 'state' in issue a5ac277614ea4d13f78031abb25ea7d6 --- a5ac277614ea4d13f78031abb25ea7d6/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/a5ac277614ea4d13f78031abb25ea7d6/state b/a5ac277614ea4d13f78031abb25ea7d6/state index 505c028..348ebd9 100644 --- a/a5ac277614ea4d13f78031abb25ea7d6/state +++ b/a5ac277614ea4d13f78031abb25ea7d6/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 0e318f1357d21419f0df6e13f79b0823de781d38 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 10:01:16 -0600 Subject: [PATCH 170/489] new description for issue 54f0eb67b05aa10763c86869ce840f33 --- 54f0eb67b05aa10763c86869ce840f33/description | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/54f0eb67b05aa10763c86869ce840f33/description b/54f0eb67b05aa10763c86869ce840f33/description index e54c1dc..bfb5ac4 100644 --- a/54f0eb67b05aa10763c86869ce840f33/description +++ b/54f0eb67b05aa10763c86869ce840f33/description @@ -1 +1,18 @@ -`ent sync` should report what changes got fetched & pushed +`ent sync` should report what changes got fetched & what changes will be pushed + +Currently `ent sync` does: + +1. `git fetch origin` +2. `git merge origin/entomologist-data` +3. `git push origin entomologist-data` + +I think after (1), before (2), we can do some easy, partially useful things: + +1. Show what we have locally that's not on the remote yet: + `git log -p entomologist-data ^origin/entomologist-data` + +2. Show what the remote has that we don't have locally: + `git log -p origin/entomologist-data ^entomologist-data ` + +Eventually (maybe after issue 5fe71e27727f4243dc997e63b9a02971) we can +display the changes in a simpler way. From 6ea0b98963e184327dc12612c8e5c1cf561a1f05 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 10:01:33 -0600 Subject: [PATCH 171/489] ent show: blank line between comment header and description This makes it a little easier to read. --- src/bin/ent/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 8bc0817..034fc4f 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -194,6 +194,7 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( println!("comment: {}", comment.uuid); println!("author: {}", comment.author); println!("timestamp: {}", comment.timestamp); + println!(""); println!("{}", comment.description); } } From efd4206b98b596860066060d4b8d565c935ac873 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 10:25:29 -0600 Subject: [PATCH 172/489] new description for issue 317ea8ccac1d414cde55771321bdec30 --- 317ea8ccac1d414cde55771321bdec30/description | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/317ea8ccac1d414cde55771321bdec30/description b/317ea8ccac1d414cde55771321bdec30/description index 1ed3eb7..429e75e 100644 --- a/317ea8ccac1d414cde55771321bdec30/description +++ b/317ea8ccac1d414cde55771321bdec30/description @@ -1,14 +1,14 @@ allow multiple read-only ent processes simultaneously -Currently every time `ent` checks out a worktree of its "issues database", -it uses the local branch `entomologist-data`. This means only one -worktree can exist at a time, because each branch can only be checked -out into at most one worktree. +Currently every time `ent` makes a worktree to read its issues database +branch, it checks out the local branch `entomologist-data`. This means +only one worktree can exist at a time, because each branch can only be +checked out into at most one worktree. -Some ent operations are read-only which means we could `git worktree add` -the `origin/entomologist-data` branch instead, which uses a detached HEAD -checkout, which is sufficient for read-only operations like `ent list` -and `ent show`. +Some ent operations are read-only which means we could `git worktree +add --detached` the `entomologist-data` branch instead, which uses a +detached HEAD checkout, which is sufficient for read-only operations like +`ent list` and `ent show`. This would be useful if you're sitting in `ent edit ${ISSUE_1}` and want to look at another issue with `ent show ${ISSUE_2}` or list the issues From f8d35e13ffb15e77e83a23551a7fd1893dba592e Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 12:39:03 -0600 Subject: [PATCH 173/489] add git::Worktree::new_detached() This makes a detached worktree, useful for read-only operations on the issues database branch. --- src/git.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/git.rs b/src/git.rs index 3374542..9765996 100644 --- a/src/git.rs +++ b/src/git.rs @@ -55,6 +55,25 @@ impl Worktree { Ok(Self { path }) } + pub fn new_detached(branch: &str) -> Result { + let path = tempfile::tempdir()?; + let result = std::process::Command::new("git") + .args([ + "worktree", + "add", + "--detach", + &path.path().to_string_lossy(), + branch, + ]) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + Ok(Self { path }) + } + pub fn path(&self) -> &std::path::Path { self.path.as_ref() } From bf4d91fa98ad309b83c72a03f716a243752aeeb1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 13:01:49 -0600 Subject: [PATCH 174/489] update 'state' in issue bd71d3896b8fa3e19cde4b5de9d9ac78 --- bd71d3896b8fa3e19cde4b5de9d9ac78/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bd71d3896b8fa3e19cde4b5de9d9ac78/state b/bd71d3896b8fa3e19cde4b5de9d9ac78/state index b6fe829..3e5126c 100644 --- a/bd71d3896b8fa3e19cde4b5de9d9ac78/state +++ b/bd71d3896b8fa3e19cde4b5de9d9ac78/state @@ -1 +1 @@ -backlog \ No newline at end of file +new \ No newline at end of file From 0b7efdfe2b54abc16da0c8a17271e263dc0ce6f1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 13:01:59 -0600 Subject: [PATCH 175/489] update 'state' in issue bd71d3896b8fa3e19cde4b5de9d9ac78 --- bd71d3896b8fa3e19cde4b5de9d9ac78/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bd71d3896b8fa3e19cde4b5de9d9ac78/state b/bd71d3896b8fa3e19cde4b5de9d9ac78/state index 3e5126c..b6fe829 100644 --- a/bd71d3896b8fa3e19cde4b5de9d9ac78/state +++ b/bd71d3896b8fa3e19cde4b5de9d9ac78/state @@ -1 +1 @@ -new \ No newline at end of file +backlog \ No newline at end of file From ce36626b7a09d436daa5eb14104bf158dc1b736c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 13:04:55 -0600 Subject: [PATCH 176/489] ent: handle issue database source & worktrees better This makes each command in `handle_command()` handle its own issues database. The commands that only need read-only access to the issues make a detached worktree, while the commands that need read-write access make a normal worktree with the `entomologist-data` branch checked out. This lets us run any number of read-only operations (like `ent list`, `ent show`, etc), even when a long-lived read-write operation (like `ent new` or `ent edit`) is running. Fixes `ent 317ea8ccac1d414cde55771321bdec30`. --- src/bin/ent/main.rs | 201 +++++++++++++++++++++++++++++++------------- 1 file changed, 143 insertions(+), 58 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 034fc4f..8493cf4 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -66,11 +66,92 @@ enum Commands { }, } -fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { +/// The main function looks at the command-line arguments and determines +/// from there where to get the Issues Database to operate on. +/// +/// * If the user specified `--issues-dir` we use that. +/// +/// * If the user specified `--issues-branch` we make sure the branch +/// exists, then use that. +/// +/// * If the user specified neither, we use the default branch +/// `entomologist-data` (after ensuring that it exists). +/// +/// * If the user specified both, it's an operator error and we abort. +/// +/// The result of that code populates an IssuesDatabaseSource object, +/// that gets used later to access the database. +enum IssuesDatabaseSource<'a> { + Dir(&'a std::path::Path), + Branch(&'a str), +} + +/// The IssuesDatabase type is a "fat path". It holds a PathBuf pointing +/// at the issues database directory, and optionally a Worktree object +/// corresponding to that path. +/// +/// The worktree field itself is never read: we put its path in `dir` +/// and that's all that the calling code cares about. +/// +/// The Worktree object is included here *when* the IssuesDatabaseSource +/// is a branch. In this case a git worktree is created to hold the +/// checkout of the branch. When the IssueDatabase object is dropped, +/// the contained/owned Worktree object is dropped, which deletes the +/// worktree directory from the filesystem and prunes the worktree from +/// git's worktree list. +struct IssuesDatabase { + dir: std::path::PathBuf, + + #[allow(dead_code)] + worktree: Option, +} + +enum IssuesDatabaseAccess { + ReadOnly, + ReadWrite, +} + +fn make_issues_database( + issues_database_source: &IssuesDatabaseSource, + access_type: IssuesDatabaseAccess, +) -> anyhow::Result { + match issues_database_source { + IssuesDatabaseSource::Dir(dir) => Ok(IssuesDatabase { + dir: std::path::PathBuf::from(dir), + worktree: None, + }), + IssuesDatabaseSource::Branch(branch) => { + let worktree = match access_type { + IssuesDatabaseAccess::ReadOnly => { + entomologist::git::Worktree::new_detached(branch)? + } + IssuesDatabaseAccess::ReadWrite => entomologist::git::Worktree::new(branch)?, + }; + Ok(IssuesDatabase { + dir: std::path::PathBuf::from(worktree.path()), + worktree: Some(worktree), + }) + } + } +} + +fn read_issues_database( + issues_database_source: &IssuesDatabaseSource, +) -> anyhow::Result { + let issues_database = + make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadOnly)?; + Ok(entomologist::issues::Issues::new_from_dir( + &issues_database.dir, + )?) +} + +fn handle_command( + args: &Args, + issues_database_source: &IssuesDatabaseSource, +) -> anyhow::Result<()> { match &args.command { Commands::List { filter } => { - let issues = - entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; + let issues = read_issues_database(issues_database_source)?; let filter = entomologist::Filter::new_from_str(filter)?; let mut uuids_by_state = std::collections::HashMap::< entomologist::issue::State, @@ -133,7 +214,9 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } Commands::New { description } => { - let mut issue = entomologist::issue::Issue::new(issues_dir)?; + let issues_database = + make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadWrite)?; + let mut issue = entomologist::issue::Issue::new(&issues_database.dir)?; let r = match description { Some(description) => issue.set_description(description), None => issue.edit_description(), @@ -148,13 +231,15 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } Ok(()) => { println!("created new issue '{}'", issue.title()); + return Ok(()); } } } Commands::Edit { issue_id } => { - let mut issues = - entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; + let issues_database = + make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadWrite)?; + let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; match issues.get_mut_issue(issue_id) { Some(issue) => match issue.edit_description() { Err(entomologist::issue::IssueError::EmptyDescription) => { @@ -173,8 +258,7 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } Commands::Show { issue_id } => { - let issues = - entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; + let issues = read_issues_database(issues_database_source)?; match issues.get_issue(issue_id) { Some(issue) => { println!("issue {}", issue_id); @@ -207,36 +291,44 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( Commands::State { issue_id, new_state, - } => { - let mut issues = - entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; - match issues.issues.get_mut(issue_id) { - Some(issue) => { - let current_state = issue.state.clone(); - match new_state { - Some(s) => { - issue.set_state(s.clone())?; - println!("issue: {}", issue_id); - println!("state: {} -> {}", current_state, s); - } - None => { - println!("issue: {}", issue_id); - println!("state: {}", current_state); - } + } => match new_state { + Some(new_state) => { + let issues_database = + make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadWrite)?; + let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; + match issues.issues.get_mut(issue_id) { + Some(issue) => { + let current_state = issue.state.clone(); + issue.set_state(new_state.clone())?; + println!("issue: {}", issue_id); + println!("state: {} -> {}", current_state, new_state); + } + None => { + return Err(anyhow::anyhow!("issue {} not found", issue_id)); } } - None => { - return Err(anyhow::anyhow!("issue {} not found", issue_id)); + } + None => { + let issues = read_issues_database(issues_database_source)?; + match issues.issues.get(issue_id) { + Some(issue) => { + println!("issue: {}", issue_id); + println!("state: {}", issue.state); + } + None => { + return Err(anyhow::anyhow!("issue {} not found", issue_id)); + } } } - } + }, Commands::Comment { issue_id, description, } => { - let mut issues = - entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; + let issues_database = + make_issues_database(issues_database_source, 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)); }; @@ -260,30 +352,25 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } Commands::Sync { remote } => { - if args.issues_dir.is_some() { + if let IssuesDatabaseSource::Branch(branch) = issues_database_source { + let issues_database = + make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadWrite)?; + entomologist::git::sync(&issues_database.dir, remote, branch)?; + println!("synced {:?} with {:?}", branch, remote); + } else { return Err(anyhow::anyhow!( "`sync` operates on a branch, don't specify `issues_dir`" )); } - // FIXME: Kinda bogus to re-do this thing we just did in - // `main()`. Maybe `main()` shouldn't create the worktree, - // maybe we should do it here in `handle_command()`? - // That way also each command could decide if it wants a - // read-only worktree or a read/write one. - let branch = match &args.issues_branch { - Some(branch) => branch, - None => "entomologist-data", - }; - entomologist::git::sync(issues_dir, remote, branch)?; - println!("synced {:?} with {:?}", branch, remote); } Commands::Assign { issue_id, new_assignee, } => { - let mut issues = - entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; + let issues_database = + make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadWrite)?; + let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; let Some(issue) = issues.issues.get_mut(issue_id) else { return Err(anyhow::anyhow!("issue {} not found", issue_id)); }; @@ -320,26 +407,24 @@ fn main() -> anyhow::Result<()> { let args: Args = Args::parse(); // println!("{:?}", args); - if let (Some(_), Some(_)) = (&args.issues_dir, &args.issues_branch) { - return Err(anyhow::anyhow!( - "don't specify both `--issues-dir` and `--issues-branch`" - )); - } + let issues_database_source = match (&args.issues_dir, &args.issues_branch) { + (Some(dir), None) => IssuesDatabaseSource::Dir(std::path::Path::new(dir)), + (None, Some(branch)) => IssuesDatabaseSource::Branch(branch), + (None, None) => IssuesDatabaseSource::Branch("entomologist-data"), + (Some(_), Some(_)) => { + return Err(anyhow::anyhow!( + "don't specify both `--issues-dir` and `--issues-branch`" + )) + } + }; - if let Some(dir) = &args.issues_dir { - let dir = std::path::Path::new(dir); - handle_command(&args, dir)?; - } else { - let branch = match &args.issues_branch { - Some(branch) => branch, - None => "entomologist-data", - }; + if let IssuesDatabaseSource::Branch(branch) = &issues_database_source { if !entomologist::git::git_branch_exists(branch)? { entomologist::git::create_orphan_branch(branch)?; } - let worktree = entomologist::git::Worktree::new(branch)?; - handle_command(&args, worktree.path())?; } + handle_command(&args, &issues_database_source)?; + Ok(()) } From f0f9e6d78834cc69b6218a077fdcc4fd46dd25b8 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 13:11:10 -0600 Subject: [PATCH 177/489] update 'assignee' in issue 54f0eb67b05aa10763c86869ce840f33 --- 54f0eb67b05aa10763c86869ce840f33/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 54f0eb67b05aa10763c86869ce840f33/assignee diff --git a/54f0eb67b05aa10763c86869ce840f33/assignee b/54f0eb67b05aa10763c86869ce840f33/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/54f0eb67b05aa10763c86869ce840f33/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From 192f5e5b1aaef816981248727ffc673eb8ce6373 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 13:11:14 -0600 Subject: [PATCH 178/489] update 'state' in issue 54f0eb67b05aa10763c86869ce840f33 --- 54f0eb67b05aa10763c86869ce840f33/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/54f0eb67b05aa10763c86869ce840f33/state b/54f0eb67b05aa10763c86869ce840f33/state index b6fe829..505c028 100644 --- a/54f0eb67b05aa10763c86869ce840f33/state +++ b/54f0eb67b05aa10763c86869ce840f33/state @@ -1 +1 @@ -backlog \ No newline at end of file +inprogress \ No newline at end of file From 3405b3d683804a849292f55b8c06a960f171e370 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 16:54:33 -0600 Subject: [PATCH 179/489] new description for issue 54f0eb67b05aa10763c86869ce840f33 --- 54f0eb67b05aa10763c86869ce840f33/description | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/54f0eb67b05aa10763c86869ce840f33/description b/54f0eb67b05aa10763c86869ce840f33/description index bfb5ac4..14218f1 100644 --- a/54f0eb67b05aa10763c86869ce840f33/description +++ b/54f0eb67b05aa10763c86869ce840f33/description @@ -14,5 +14,6 @@ I think after (1), before (2), we can do some easy, partially useful things: 2. Show what the remote has that we don't have locally: `git log -p origin/entomologist-data ^entomologist-data ` -Eventually (maybe after issue 5fe71e27727f4243dc997e63b9a02971) we can -display the changes in a simpler way. +Eventually (maybe after issue 5fe71e27727f4243dc997e63b9a02971 and/or +issue fd81241f795333b64e7911cfb1b57c8f) we can display the changes in +a simpler way. From 9e52868d09e8f69ec353dc651107fb1b41f6ed5a Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 17:14:03 -0600 Subject: [PATCH 180/489] update 'assignee' in issue fd81241f795333b64e7911cfb1b57c8f --- fd81241f795333b64e7911cfb1b57c8f/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 fd81241f795333b64e7911cfb1b57c8f/assignee diff --git a/fd81241f795333b64e7911cfb1b57c8f/assignee b/fd81241f795333b64e7911cfb1b57c8f/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/fd81241f795333b64e7911cfb1b57c8f/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From e3a47c5c37da1d5cb1f1063c2c6edba4e19bf8a2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 17:14:09 -0600 Subject: [PATCH 181/489] update 'state' in issue fd81241f795333b64e7911cfb1b57c8f --- fd81241f795333b64e7911cfb1b57c8f/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fd81241f795333b64e7911cfb1b57c8f/state b/fd81241f795333b64e7911cfb1b57c8f/state index b6fe829..505c028 100644 --- a/fd81241f795333b64e7911cfb1b57c8f/state +++ b/fd81241f795333b64e7911cfb1b57c8f/state @@ -1 +1 @@ -backlog \ No newline at end of file +inprogress \ No newline at end of file From 4262c26edd73f5e222e3c4f49c26435c5c47f0e1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 17:32:13 -0600 Subject: [PATCH 182/489] new description for issue fd81241f795333b64e7911cfb1b57c8f --- fd81241f795333b64e7911cfb1b57c8f/description | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fd81241f795333b64e7911cfb1b57c8f/description b/fd81241f795333b64e7911cfb1b57c8f/description index 1a26aee..32c6b39 100644 --- a/fd81241f795333b64e7911cfb1b57c8f/description +++ b/fd81241f795333b64e7911cfb1b57c8f/description @@ -6,3 +6,6 @@ It'd be nice to be able to look at the `git log --oneline` of an issue dir and s * changing state or assignee * adding a comment * editing a comment + +I think each kind of log message we want will correspond to a matching +API function in the entomologist crate. From 7b6efdf9254d430f08c207de6dec21013f3cfb84 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 10:01:05 -0600 Subject: [PATCH 183/489] rename git::add_file() to just add(), it can add directories too --- src/comment.rs | 2 +- src/git.rs | 2 +- src/issue.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/comment.rs b/src/comment.rs index e6c95fd..c7afe44 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -95,7 +95,7 @@ impl Comment { return Err(CommentError::EditorError); } if description_filename.exists() && description_filename.metadata()?.len() > 0 { - crate::git::add_file(&description_filename)?; + crate::git::add(&description_filename)?; } else { // User saved an empty file, which means they changed their // mind and no longer want to edit the description. diff --git a/src/git.rs b/src/git.rs index 9765996..0ea0ee6 100644 --- a/src/git.rs +++ b/src/git.rs @@ -136,7 +136,7 @@ pub fn worktree_is_dirty(dir: &str) -> Result { return Ok(result.stdout.len() > 0); } -pub fn add_file(file: &std::path::Path) -> Result<(), GitError> { +pub fn add(file: &std::path::Path) -> Result<(), GitError> { let result = std::process::Command::new("git") .args(["add", &file.to_string_lossy()]) .current_dir(file.parent().unwrap()) diff --git a/src/issue.rs b/src/issue.rs index 7201871..3ea6afe 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -230,7 +230,7 @@ impl Issue { return Err(IssueError::EditorError); } if description_filename.exists() && description_filename.metadata()?.len() > 0 { - crate::git::add_file(&description_filename)?; + crate::git::add(&description_filename)?; } else { // User saved an empty file, which means they changed their // mind and no longer want to edit the description. From 1477322f81cedde312c1169b1f56f6b2258d2f94 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 10:06:17 -0600 Subject: [PATCH 184/489] Issue: refactor to simplify & make better git log messages This starts cleaning up the Issue API. * Start separating the public API from the internal API. * Make `Issue::new()` and `Issue::edit_description()` better behaved, simpler, reduce code duplication, and also produce better git log messages. * Update `ent` to call the changed `new()` function. * Add some comments documenting the Issue API. * `ent new` and `ent edit` now use the editor specified by the EDITOR environment variable, if any. Defaults to `vi` if unspecified. --- src/bin/ent/main.rs | 9 +-- src/issue.rs | 151 +++++++++++++++++++++++++++++--------------- 2 files changed, 103 insertions(+), 57 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 8493cf4..1bc66ba 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -216,12 +216,7 @@ fn handle_command( Commands::New { description } => { let issues_database = make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadWrite)?; - let mut issue = entomologist::issue::Issue::new(&issues_database.dir)?; - let r = match description { - Some(description) => issue.set_description(description), - None => issue.edit_description(), - }; - match r { + match entomologist::issue::Issue::new(&issues_database.dir, description) { Err(entomologist::issue::IssueError::EmptyDescription) => { println!("no new issue created"); return Ok(()); @@ -229,7 +224,7 @@ fn handle_command( Err(e) => { return Err(e.into()); } - Ok(()) => { + Ok(issue) => { println!("created new issue '{}'", issue.title()); return Ok(()); } diff --git a/src/issue.rs b/src/issue.rs index 3ea6afe..5167e4b 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -38,6 +38,8 @@ pub enum IssueError { #[error(transparent)] StdIoError(#[from] std::io::Error), #[error(transparent)] + EnvVarError(#[from] std::env::VarError), + #[error(transparent)] CommentError(#[from] crate::comment::CommentError), #[error("Failed to parse issue")] IssueParseError, @@ -87,6 +89,7 @@ impl fmt::Display for State { } } +// This is the public API of Issue. impl Issue { pub fn new_from_dir(dir: &std::path::Path) -> Result { let mut description: Option = None; @@ -179,12 +182,26 @@ impl Issue { }) } - pub fn new(dir: &std::path::Path) -> Result { + /// Create a new Issue in an Issues database specified by a directory. + /// The new Issue will live in a new subdirectory, named by a unique + /// Issue identifier. + /// + /// If a description string is supplied, the new Issue's description + /// will be initialized from it with no user interaction. + /// + /// If no description is supplied, the user will be prompted to + /// input one into an editor. + /// + /// On success, the new Issue with its valid description is committed + /// to the Issues database. + pub fn new(dir: &std::path::Path, description: &Option) -> Result { let mut issue_dir = std::path::PathBuf::from(dir); let rnd: u128 = rand::random(); - issue_dir.push(&format!("{:032x}", rnd)); + let issue_id = format!("{:032x}", rnd); + issue_dir.push(&issue_id); std::fs::create_dir(&issue_dir)?; - Ok(Self { + + let mut issue = Self { author: String::from(""), timestamp: chrono::Local::now(), state: State::New, @@ -192,58 +209,38 @@ impl Issue { assignee: None, description: String::from(""), // FIXME: kind of bogus to use the empty string as None comments: Vec::::new(), - dir: issue_dir, - }) - } + dir: issue_dir.clone(), + }; - pub fn set_description(&mut self, description: &str) -> Result<(), IssueError> { - if description.len() == 0 { - return Err(IssueError::EmptyDescription); - } - self.description = String::from(description); - let mut description_filename = std::path::PathBuf::from(&self.dir); - description_filename.push("description"); - let mut description_file = std::fs::File::create(&description_filename)?; - write!(description_file, "{}", description)?; - crate::git::git_commit_file(&description_filename)?; - Ok(()) - } - - pub fn read_description(&mut self) -> Result<(), IssueError> { - let mut description_filename = std::path::PathBuf::from(&self.dir); - description_filename.push("description"); - self.description = std::fs::read_to_string(description_filename)?; - Ok(()) - } - - pub fn edit_description(&mut self) -> Result<(), IssueError> { - let mut description_filename = std::path::PathBuf::from(&self.dir); - description_filename.push("description"); - let exists = description_filename.exists(); - let result = std::process::Command::new("vi") - .arg(&description_filename.as_mut_os_str()) - .spawn()? - .wait_with_output()?; - if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); - return Err(IssueError::EditorError); - } - if description_filename.exists() && description_filename.metadata()?.len() > 0 { - crate::git::add(&description_filename)?; - } else { - // User saved an empty file, which means they changed their - // mind and no longer want to edit the description. - if exists { - crate::git::restore_file(&description_filename)?; + match description { + Some(description) => { + if description.len() == 0 { + return Err(IssueError::EmptyDescription); + } + issue.description = String::from(description); + let description_filename = issue.description_filename(); + let mut description_file = std::fs::File::create(&description_filename)?; + write!(description_file, "{}", description)?; } - return Err(IssueError::EmptyDescription); - } + None => issue.edit_description_file()?, + }; + + crate::git::add(&issue_dir)?; + crate::git::commit(&issue_dir, &format!("create new issue {}", issue_id))?; + + Ok(issue) + } + + /// Interactively edit the description of an existing Issue. + pub fn edit_description(&mut self) -> Result<(), IssueError> { + self.edit_description_file()?; + let description_filename = self.description_filename(); + crate::git::add(&description_filename)?; if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { crate::git::commit( &description_filename.parent().unwrap(), &format!( - "new description for issue {}", + "edit description of issue {}", description_filename .parent() .unwrap() @@ -252,11 +249,11 @@ impl Issue { .to_string_lossy() ), )?; - self.read_description()?; } Ok(()) } + /// Return the Issue title (first line of the description). pub fn title<'a>(&'a self) -> &'a str { match self.description.find("\n") { Some(index) => &self.description.as_str()[..index], @@ -291,6 +288,60 @@ impl Issue { } } +// This is the internal/private API of Issue. +impl Issue { + fn description_filename(&self) -> std::path::PathBuf { + let mut description_filename = std::path::PathBuf::from(&self.dir); + description_filename.push("description"); + description_filename + } + + /// Read the Issue's description file into the internal Issue representation. + fn read_description(&mut self) -> Result<(), IssueError> { + let description_filename = self.description_filename(); + self.description = std::fs::read_to_string(description_filename)?; + Ok(()) + } + + /// Opens the Issue's `description` file in an editor. Validates the + /// editor's exit code. Updates the Issue's internal description + /// from what the user saved in the file. + /// + /// Used by Issue::new() when no description is supplied, and also + /// used by `ent edit ISSUE`. + fn edit_description_file(&mut self) -> Result<(), IssueError> { + let description_filename = self.description_filename(); + let exists = description_filename.exists(); + let editor = match std::env::var("EDITOR") { + Ok(editor) => editor, + Err(std::env::VarError::NotPresent) => String::from("vi"), + Err(e) => return Err(e.into()), + }; + let result = std::process::Command::new(editor) + .arg(&description_filename.as_os_str()) + .spawn()? + .wait_with_output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(IssueError::EditorError); + } + if !description_filename.exists() || description_filename.metadata()?.len() == 0 { + // User saved an empty file, or exited without saving while + // editing a new description file. Both means they changed + // their mind and no longer want to edit the description. + if exists { + // File existed before the user emptied it, so restore + // the original. + crate::git::restore_file(&description_filename)?; + } + return Err(IssueError::EmptyDescription); + } + self.read_description()?; + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; From ab86e6369c87d0f2f483788808ee92fcecd6c270 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 11:08:17 -0600 Subject: [PATCH 185/489] Issue::set_state(): better git commit message --- src/issue.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/issue.rs b/src/issue.rs index 5167e4b..9b5ee18 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -261,12 +261,23 @@ impl Issue { } } + /// Change the State of the Issue. pub fn set_state(&mut self, new_state: State) -> Result<(), IssueError> { let mut state_filename = std::path::PathBuf::from(&self.dir); state_filename.push("state"); let mut state_file = std::fs::File::create(&state_filename)?; write!(state_file, "{}", new_state)?; - crate::git::git_commit_file(&state_filename)?; + crate::git::add(&state_filename)?; + if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { + crate::git::commit( + &self.dir, + &format!( + "change state of issue {} to {}", + self.dir.file_name().unwrap().to_string_lossy(), + new_state, + ), + )?; + } Ok(()) } From 86b6a527e446c7121714467f704f5b98287e6eaf Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 11:54:56 -0600 Subject: [PATCH 186/489] change state of issue 317ea8ccac1d414cde55771321bdec30 to done --- 317ea8ccac1d414cde55771321bdec30/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/317ea8ccac1d414cde55771321bdec30/state b/317ea8ccac1d414cde55771321bdec30/state index 505c028..348ebd9 100644 --- a/317ea8ccac1d414cde55771321bdec30/state +++ b/317ea8ccac1d414cde55771321bdec30/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From cdb0f450d94b5daab31b55de5e0a4c14b8d0b632 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 11:55:55 -0600 Subject: [PATCH 187/489] edit description of issue 9502937b139b797f94eb422651417757 --- 9502937b139b797f94eb422651417757/description | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9502937b139b797f94eb422651417757/description b/9502937b139b797f94eb422651417757/description index 5dbd141..2452fce 100644 --- a/9502937b139b797f94eb422651417757/description +++ b/9502937b139b797f94eb422651417757/description @@ -1,4 +1,4 @@ -visually distinguish issues and comments from each other +`ent show`: visually distinguish issues and comments from each other `git log` does it well: From 1c8d994fd9cb798f1ac6d366e7c294b90a004969 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 12:39:48 -0600 Subject: [PATCH 188/489] better `ent assign` log message --- src/bin/ent/main.rs | 37 +++++++++++++++++++------------------ src/issue.rs | 18 +++++++++++++++++- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 1bc66ba..11f74a4 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -363,31 +363,32 @@ fn handle_command( issue_id, new_assignee, } => { - let issues_database = - make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadWrite)?; - let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; - let Some(issue) = issues.issues.get_mut(issue_id) else { + let issues = 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)); }; - match (&issue.assignee, new_assignee) { - (Some(old_assignee), Some(new_assignee)) => { - println!("issue: {}", 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 = make_issues_database( + issues_database_source, + 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)?; } - (Some(old_assignee), None) => { - println!("issue: {}", issue_id); + None => { println!("assignee: {}", old_assignee); } - (None, Some(new_assignee)) => { - println!("issue: {}", issue_id); - println!("assignee: None -> {}", new_assignee); - issue.set_assignee(new_assignee)?; - } - (None, None) => { - println!("issue: {}", issue_id); - println!("assignee: None"); - } } } } diff --git a/src/issue.rs b/src/issue.rs index 9b5ee18..d83cf99 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -289,12 +289,28 @@ impl Issue { Ok(()) } + /// Set the Assignee of an Issue. pub fn set_assignee(&mut self, new_assignee: &str) -> Result<(), IssueError> { + let old_assignee = match &self.assignee { + Some(assignee) => assignee.clone(), + None => String::from("None"), + }; let mut assignee_filename = std::path::PathBuf::from(&self.dir); assignee_filename.push("assignee"); let mut assignee_file = std::fs::File::create(&assignee_filename)?; write!(assignee_file, "{}", new_assignee)?; - crate::git::git_commit_file(&assignee_filename)?; + crate::git::add(&assignee_filename)?; + if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { + crate::git::commit( + &self.dir, + &format!( + "change assignee of issue {}, {} -> {}", + self.dir.file_name().unwrap().to_string_lossy(), + old_assignee, + new_assignee, + ), + )?; + } Ok(()) } } From d642004ee08fbc5bac08faae2012d766453b01ef Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 11:48:39 -0600 Subject: [PATCH 189/489] refactor `ent comment` Instead of a two-step process handled by the application (`Issue::new_comment()` and `Comment::set_description()` or `Comment::edit_description()`), make a simpler-to-use single-step `Issue::add_comment()`. Move the implementation details from Issue to Comment. Better log message when adding a comment. --- src/bin/ent/main.rs | 18 +++---- src/comment.rs | 127 +++++++++++++++++++++++++++++++++----------- src/issue.rs | 26 +++------ 3 files changed, 111 insertions(+), 60 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 11f74a4..d2b5445 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -327,21 +327,21 @@ fn handle_command( let Some(issue) = issues.get_mut_issue(issue_id) else { return Err(anyhow::anyhow!("issue {} not found", issue_id)); }; - let mut comment = issue.new_comment()?; - let r = match description { - Some(description) => comment.set_description(description), - None => comment.edit_description(), - }; - match r { - Err(entomologist::comment::CommentError::EmptyDescription) => { + match issue.add_comment(description) { + Err(entomologist::issue::IssueError::CommentError( + entomologist::comment::CommentError::EmptyDescription, + )) => { println!("aborted new comment"); return Ok(()); } Err(e) => { return Err(e.into()); } - Ok(()) => { - println!("created new comment {}", &comment.uuid); + Ok(comment) => { + println!( + "created new comment {} on issue {}", + &comment.uuid, &issue_id + ); } } } diff --git a/src/comment.rs b/src/comment.rs index c7afe44..e9c3134 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -16,6 +16,8 @@ pub struct Comment { pub enum CommentError { #[error(transparent)] StdIoError(#[from] std::io::Error), + #[error(transparent)] + EnvVarError(#[from] std::env::VarError), #[error("Failed to parse comment")] CommentParseError, #[error("Failed to run git")] @@ -61,17 +63,56 @@ impl Comment { }) } - pub fn set_description(&mut self, description: &str) -> Result<(), CommentError> { - if description.len() == 0 { - return Err(CommentError::EmptyDescription); + /// Create a new Comment on the specified Issue. Commits. + pub fn new( + issue: &crate::issue::Issue, + description: &Option, + ) -> Result { + let mut dir = std::path::PathBuf::from(&issue.dir); + dir.push("comments"); + if !dir.exists() { + std::fs::create_dir(&dir)?; } - self.description = String::from(description); - let mut description_filename = std::path::PathBuf::from(&self.dir); - description_filename.push("description"); - let mut description_file = std::fs::File::create(&description_filename)?; - write!(description_file, "{}", description)?; - crate::git::git_commit_file(&description_filename)?; - Ok(()) + + let rnd: u128 = rand::random(); + let uuid = format!("{:032x}", rnd); + dir.push(&uuid); + std::fs::create_dir(&dir)?; + + let mut comment = crate::comment::Comment { + uuid, + author: String::from(""), // this will be updated from git when we re-read this comment + timestamp: chrono::Local::now(), + description: String::from(""), // this will be set immediately below + dir: dir.clone(), + }; + + match description { + Some(description) => { + if description.len() == 0 { + return Err(CommentError::EmptyDescription); + } + comment.description = String::from(description); + let description_filename = comment.description_filename(); + let mut description_file = std::fs::File::create(&description_filename)?; + write!(description_file, "{}", description)?; + } + None => comment.edit_description_file()?, + }; + + crate::git::add(&dir)?; + if crate::git::worktree_is_dirty(&dir.to_string_lossy())? { + crate::git::commit( + &dir, + &format!( + "add comment {} on issue {}", + comment.uuid, + issue.dir.file_name().unwrap().to_string_lossy(), + ), + )?; + } + + Ok(comment) } pub fn read_description(&mut self) -> Result<(), CommentError> { @@ -82,11 +123,39 @@ impl Comment { } pub fn edit_description(&mut self) -> Result<(), CommentError> { - let mut description_filename = std::path::PathBuf::from(&self.dir); - description_filename.push("description"); + self.edit_description_file()?; + let description_filename = self.description_filename(); + crate::git::add(&description_filename)?; + if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { + crate::git::commit( + &description_filename.parent().unwrap(), + &format!( + "edit comment {} on issue FIXME", // FIXME: name the issue that the comment is on + self.dir.file_name().unwrap().to_string_lossy() + ), + )?; + self.read_description()?; + } + Ok(()) + } + + /// Opens the Comment's `description` file in an editor. Validates + /// the editor's exit code. Updates the Comment's internal + /// description from what the user saved in the file. + /// + /// Used by Issue::add_comment() when no description is supplied, + /// and (FIXME: in the future) used by `ent edit COMMENT`. + pub fn edit_description_file(&mut self) -> Result<(), CommentError> { + let description_filename = self.description_filename(); let exists = description_filename.exists(); - let result = std::process::Command::new("vi") - .arg(&description_filename.as_mut_os_str()) + + let editor = match std::env::var("EDITOR") { + Ok(editor) => editor, + Err(std::env::VarError::NotPresent) => String::from("vi"), + Err(e) => return Err(e.into()), + }; + let result = std::process::Command::new(editor) + .arg(&description_filename.as_os_str()) .spawn()? .wait_with_output()?; if !result.status.success() { @@ -94,9 +163,8 @@ impl Comment { println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); return Err(CommentError::EditorError); } - if description_filename.exists() && description_filename.metadata()?.len() > 0 { - crate::git::add(&description_filename)?; - } else { + + if !description_filename.exists() || description_filename.metadata()?.len() == 0 { // User saved an empty file, which means they changed their // mind and no longer want to edit the description. if exists { @@ -104,25 +172,20 @@ impl Comment { } return Err(CommentError::EmptyDescription); } - if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { - crate::git::commit( - &description_filename.parent().unwrap(), - &format!( - "new description for comment {}", - description_filename - .parent() - .unwrap() - .file_name() - .unwrap() - .to_string_lossy() - ), - )?; - self.read_description()?; - } + self.read_description()?; Ok(()) } } +// This is the private, internal API. +impl Comment { + fn description_filename(&self) -> std::path::PathBuf { + let mut description_filename = std::path::PathBuf::from(&self.dir); + description_filename.push("description"); + description_filename + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/issue.rs b/src/issue.rs index d83cf99..15d97f0 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -161,25 +161,13 @@ impl Issue { Ok(()) } - pub fn new_comment(&mut self) -> Result { - let mut dir = std::path::PathBuf::from(&self.dir); - dir.push("comments"); - if !dir.exists() { - std::fs::create_dir(&dir)?; - } - - let rnd: u128 = rand::random(); - let uuid = format!("{:032x}", rnd); - dir.push(&uuid); - std::fs::create_dir(&dir)?; - - Ok(crate::comment::Comment { - uuid, - author: String::from("Sebastian Kuzminsky "), - timestamp: chrono::Local::now(), - description: String::from(""), // FIXME - dir, - }) + /// Add a new Comment to the Issue. Commits. + pub fn add_comment( + &mut self, + description: &Option, + ) -> Result { + let comment = crate::comment::Comment::new(self, description)?; + Ok(comment) } /// Create a new Issue in an Issues database specified by a directory. From 08e069841809bb052b94de3b7c5c79cb47597bb9 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 10 Jul 2025 16:27:59 -0600 Subject: [PATCH 190/489] git::sync(): show somewhat ugly git logs of stuff fetched and pushed --- src/git.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/git.rs b/src/git.rs index 0ea0ee6..63cb004 100644 --- a/src/git.rs +++ b/src/git.rs @@ -233,6 +233,62 @@ pub fn sync(dir: &std::path::Path, remote: &str, branch: &str) -> Result<(), Git git_fetch(dir, remote)?; + // FIXME: Possible things to add: + // * `git log -p` shows diff + // * `git log --numstat` shows machine-readable diffstat + + // Show what we just fetched from the remote. + let result = std::process::Command::new("git") + .args([ + "log", + "--no-merges", + "--pretty=format:%an: %s", + &format!("{}/{}", remote, branch), + &format!("^{}", branch), + ]) + .current_dir(dir) + .output()?; + if !result.status.success() { + println!( + "Sync failed! 'git log' error! Help, a human needs to fix the mess in {:?}", + dir + ); + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + if result.stdout.len() > 0 { + println!("Changes fetched from remote {}:", remote); + println!("{}", std::str::from_utf8(&result.stdout).unwrap()); + println!(""); + } + + // Show what we are about to push to the remote. + let result = std::process::Command::new("git") + .args([ + "log", + "--no-merges", + "--pretty=format:%an: %s", + &format!("{}", branch), + &format!("^{}/{}", remote, branch), + ]) + .current_dir(dir) + .output()?; + if !result.status.success() { + println!( + "Sync failed! 'git log' error! Help, a human needs to fix the mess in {:?}", + dir + ); + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + if result.stdout.len() > 0 { + println!("Changes to push to remote {}:", remote); + println!("{}", std::str::from_utf8(&result.stdout).unwrap()); + println!(""); + } + // Merge remote branch into local. let result = std::process::Command::new("git") .args(["merge", &format!("{}/{}", remote, branch)]) From 9e36ea82a994443ee696900898c9d378efbefb3f Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 13:17:03 -0600 Subject: [PATCH 191/489] change assignee of issue 7da3bd5b72de0a05936b094db5d24304, None -> seb --- 7da3bd5b72de0a05936b094db5d24304/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 7da3bd5b72de0a05936b094db5d24304/assignee diff --git a/7da3bd5b72de0a05936b094db5d24304/assignee b/7da3bd5b72de0a05936b094db5d24304/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/7da3bd5b72de0a05936b094db5d24304/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From a5f536461abfc123cf90906d805be6ffc7badc47 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 13:17:24 -0600 Subject: [PATCH 192/489] change state of issue 7da3bd5b72de0a05936b094db5d24304 to inprogress --- 7da3bd5b72de0a05936b094db5d24304/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7da3bd5b72de0a05936b094db5d24304/state b/7da3bd5b72de0a05936b094db5d24304/state index b6fe829..505c028 100644 --- a/7da3bd5b72de0a05936b094db5d24304/state +++ b/7da3bd5b72de0a05936b094db5d24304/state @@ -1 +1 @@ -backlog \ No newline at end of file +inprogress \ No newline at end of file From 676a8ff168306b99d94922c2dbde89622162dc03 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 14:22:40 -0600 Subject: [PATCH 193/489] edit comment 87919ac6e79521732b0ec65fa7f79853 on issue FIXME --- .../comments/87919ac6e79521732b0ec65fa7f79853/description | 2 ++ 1 file changed, 2 insertions(+) diff --git a/7da3bd5b72de0a05936b094db5d24304/comments/87919ac6e79521732b0ec65fa7f79853/description b/7da3bd5b72de0a05936b094db5d24304/comments/87919ac6e79521732b0ec65fa7f79853/description index 18b29d3..cfe450f 100644 --- a/7da3bd5b72de0a05936b094db5d24304/comments/87919ac6e79521732b0ec65fa7f79853/description +++ b/7da3bd5b72de0a05936b094db5d24304/comments/87919ac6e79521732b0ec65fa7f79853/description @@ -1,2 +1,4 @@ I think this will be easy. We'll teach `ent edit ID` to search not only issue IDs but also comment IDs, then reuse the existing edit code. + +Yup, turned out to be exactly that easy. :-) From 3d5f7110e4ca505f986a738d65a1d72ac16b0097 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 14:45:46 -0600 Subject: [PATCH 194/489] edit description of issue 08f0d7ee7842c439382816d21ec1dea2 --- 08f0d7ee7842c439382816d21ec1dea2/description | 23 ++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/08f0d7ee7842c439382816d21ec1dea2/description b/08f0d7ee7842c439382816d21ec1dea2/description index a6bd3e8..203c6c6 100644 --- a/08f0d7ee7842c439382816d21ec1dea2/description +++ b/08f0d7ee7842c439382816d21ec1dea2/description @@ -35,11 +35,26 @@ or a due date this week. ## Done last week -I can imagine finding the issues with state=done, running `git log` -on their state file, finding the datetime of the transition to done, -and selecting the ones where that datetime is in a particular time window. +I can imagine finding the issues with state=done, running `git log` on +their state file, finding the datetime of the transition to done, and +selecting the ones where that datetime is in a particular time window. -Not sure how to express to ent that that is what i want though. +This has the drawback that it's hard to lie about... With taskwarrior +I often found myself finishing a task on Week X, but forgetting to mark +it complete. Then the Monday of Week X+1 i would mark it complete. +If i didn't override the completion-date the task would look like it +was completed on Week X+1, not Week X like i wanted. + +In git we have `git commit --date=DATE`, but that's only usable at +commit-time (and awkward to express to ent). We can rewrite history with +`git rebase`, but only until we `ent sync`. + +Maybe the `state` file should have a date in it, in addition to the +state word? Or maybe `completion-date` should be a key in a per-issue +key-value store? Is that kv store related to tags? Idk... + +Not sure how to express to ent what completion-dates i want to see. +Maybe a new filter type? `ent list finished=2025-07-01..now`? Maybe we can use git tags in the entomologist-data branch somehow? `git log` between tags and look for "state: _ -> done". But how to From 3b350cf1000da642d7a0616d83c38bbc67683262 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 14:46:01 -0600 Subject: [PATCH 195/489] create new issue 20649f4f970201e04ea50e2b460cff17 --- 20649f4f970201e04ea50e2b460cff17/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 20649f4f970201e04ea50e2b460cff17/description diff --git a/20649f4f970201e04ea50e2b460cff17/description b/20649f4f970201e04ea50e2b460cff17/description new file mode 100644 index 0000000..282b2d6 --- /dev/null +++ b/20649f4f970201e04ea50e2b460cff17/description @@ -0,0 +1 @@ +add \ No newline at end of file From 5c5ad10179099595016bb3f8175dcae6a004e5d8 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 14:46:22 -0600 Subject: [PATCH 196/489] create new issue ca164f570ea3d62f2ff4a001200e0a00 --- ca164f570ea3d62f2ff4a001200e0a00/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 ca164f570ea3d62f2ff4a001200e0a00/description diff --git a/ca164f570ea3d62f2ff4a001200e0a00/description b/ca164f570ea3d62f2ff4a001200e0a00/description new file mode 100644 index 0000000..4a0f820 --- /dev/null +++ b/ca164f570ea3d62f2ff4a001200e0a00/description @@ -0,0 +1 @@ +add `ent sync --dry-run` \ No newline at end of file From dc2862bed3b2dc62b150627ee5c2f9bcd8099ccc Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 14:47:04 -0600 Subject: [PATCH 197/489] change state of issue 20649f4f970201e04ea50e2b460cff17 to wontdo --- 20649f4f970201e04ea50e2b460cff17/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 20649f4f970201e04ea50e2b460cff17/state diff --git a/20649f4f970201e04ea50e2b460cff17/state b/20649f4f970201e04ea50e2b460cff17/state new file mode 100644 index 0000000..9e3b09d --- /dev/null +++ b/20649f4f970201e04ea50e2b460cff17/state @@ -0,0 +1 @@ +wontdo \ No newline at end of file From 1cb4c08db239d65cf733576a26dfc040a446448d Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 14:49:39 -0600 Subject: [PATCH 198/489] add comment 2a9e1d6e52d025992bc7c8c22fd20474 on issue 20649f4f970201e04ea50e2b460cff17 --- .../comments/2a9e1d6e52d025992bc7c8c22fd20474/description | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 20649f4f970201e04ea50e2b460cff17/comments/2a9e1d6e52d025992bc7c8c22fd20474/description diff --git a/20649f4f970201e04ea50e2b460cff17/comments/2a9e1d6e52d025992bc7c8c22fd20474/description b/20649f4f970201e04ea50e2b460cff17/comments/2a9e1d6e52d025992bc7c8c22fd20474/description new file mode 100644 index 0000000..1cb68a4 --- /dev/null +++ b/20649f4f970201e04ea50e2b460cff17/comments/2a9e1d6e52d025992bc7c8c22fd20474/description @@ -0,0 +1,8 @@ +Oops, i stupidly ran: ent new "add `ent sync --dry-run`" + +Because i used double-quotes, bash saw the `...` and ran that as a +command, which failed, but then bash ran the result anyway ("ent new +"add "), and here we are. + +This would be a good time for `ent rm` as suggested by issue +1ebdee0502937bf934bb0d72256dbdd1. From d426dde73045231de750939516c37ba058e33b52 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 15:06:00 -0600 Subject: [PATCH 199/489] change state of issue ca164f570ea3d62f2ff4a001200e0a00 to backlog --- ca164f570ea3d62f2ff4a001200e0a00/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 ca164f570ea3d62f2ff4a001200e0a00/state diff --git a/ca164f570ea3d62f2ff4a001200e0a00/state b/ca164f570ea3d62f2ff4a001200e0a00/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/ca164f570ea3d62f2ff4a001200e0a00/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 5ba21702a0a41d168e546a50f62ff4b97adae67c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 15:09:30 -0600 Subject: [PATCH 200/489] add comment 9bafbb1050f33fd31c4c112c06eea4d9 on issue 7d2d236668872cf11f167ac0462f8751 --- .../comments/9bafbb1050f33fd31c4c112c06eea4d9/description | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 7d2d236668872cf11f167ac0462f8751/comments/9bafbb1050f33fd31c4c112c06eea4d9/description diff --git a/7d2d236668872cf11f167ac0462f8751/comments/9bafbb1050f33fd31c4c112c06eea4d9/description b/7d2d236668872cf11f167ac0462f8751/comments/9bafbb1050f33fd31c4c112c06eea4d9/description new file mode 100644 index 0000000..e224997 --- /dev/null +++ b/7d2d236668872cf11f167ac0462f8751/comments/9bafbb1050f33fd31c4c112c06eea4d9/description @@ -0,0 +1,2 @@ +Tags as described above seem useful, but also key/value pairs seem useful, +and similar. Maybe a tag is a key with a Null value? From 65316da0bda0e7c488d57ab2dea28e262c5959f2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 14:24:57 -0600 Subject: [PATCH 201/489] teach `ent edit` to edit Comments as well as Issues --- src/bin/ent/main.rs | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index d2b5445..655d16a 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -32,8 +32,8 @@ enum Commands { /// Create a new issue. New { description: Option }, - /// Edit the description of an issue. - Edit { issue_id: String }, + /// Edit the description of an Issue or a Comment. + Edit { uuid: String }, /// Show the full description of an issue. Show { issue_id: String }, @@ -231,25 +231,39 @@ fn handle_command( } } - Commands::Edit { issue_id } => { + Commands::Edit { uuid } => { let issues_database = make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadWrite)?; let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; - match issues.get_mut_issue(issue_id) { - Some(issue) => match issue.edit_description() { + if let Some(issue) = issues.get_mut_issue(uuid) { + match issue.edit_description() { Err(entomologist::issue::IssueError::EmptyDescription) => { println!("aborted issue edit"); return Ok(()); } - Err(e) => { - return Err(e.into()); - } - Ok(()) => (), - }, - None => { - return Err(anyhow::anyhow!("issue {} not found", issue_id)); + Err(e) => return Err(e.into()), + Ok(()) => return Ok(()), } } + // No issue by that ID, check all the comments. + for (_, issue) in issues.issues.iter_mut() { + for comment in issue.comments.iter_mut() { + if comment.uuid == *uuid { + match comment.edit_description() { + Err(entomologist::comment::CommentError::EmptyDescription) => { + println!("aborted comment edit"); + return Ok(()); + } + Err(e) => return Err(e.into()), + Ok(()) => return Ok(()), + } + } + } + } + return Err(anyhow::anyhow!( + "no issue or comment with uuid {} found", + uuid + )); } Commands::Show { issue_id } => { From 42f407729cd111a86c30165965360bc516526fd9 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 20:32:23 -0600 Subject: [PATCH 202/489] change state of issue 7da3bd5b72de0a05936b094db5d24304 to done --- 7da3bd5b72de0a05936b094db5d24304/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7da3bd5b72de0a05936b094db5d24304/state b/7da3bd5b72de0a05936b094db5d24304/state index 505c028..348ebd9 100644 --- a/7da3bd5b72de0a05936b094db5d24304/state +++ b/7da3bd5b72de0a05936b094db5d24304/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From f6e8e42a8b21d7b692bdf9e71914cee6add28dcc Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 20:32:31 -0600 Subject: [PATCH 203/489] change state of issue fd81241f795333b64e7911cfb1b57c8f to done --- fd81241f795333b64e7911cfb1b57c8f/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fd81241f795333b64e7911cfb1b57c8f/state b/fd81241f795333b64e7911cfb1b57c8f/state index 505c028..348ebd9 100644 --- a/fd81241f795333b64e7911cfb1b57c8f/state +++ b/fd81241f795333b64e7911cfb1b57c8f/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 0b40bc872227b409e32fd438d2b2d12efdfcc936 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 20:32:59 -0600 Subject: [PATCH 204/489] change state of issue 54f0eb67b05aa10763c86869ce840f33 to done --- 54f0eb67b05aa10763c86869ce840f33/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/54f0eb67b05aa10763c86869ce840f33/state b/54f0eb67b05aa10763c86869ce840f33/state index 505c028..348ebd9 100644 --- a/54f0eb67b05aa10763c86869ce840f33/state +++ b/54f0eb67b05aa10763c86869ce840f33/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 4deda0d435e51c098d0c5c7e024791f404e29860 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 20:35:56 -0600 Subject: [PATCH 205/489] change assignee of issue 7d2d236668872cf11f167ac0462f8751, None -> seb --- 7d2d236668872cf11f167ac0462f8751/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 7d2d236668872cf11f167ac0462f8751/assignee diff --git a/7d2d236668872cf11f167ac0462f8751/assignee b/7d2d236668872cf11f167ac0462f8751/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/7d2d236668872cf11f167ac0462f8751/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From 7af54a8e37d815b5f2d9056f9cbfac1f417082c1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 11 Jul 2025 20:36:07 -0600 Subject: [PATCH 206/489] change state of issue 7d2d236668872cf11f167ac0462f8751 to inprogress --- 7d2d236668872cf11f167ac0462f8751/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7d2d236668872cf11f167ac0462f8751/state b/7d2d236668872cf11f167ac0462f8751/state index b6fe829..505c028 100644 --- a/7d2d236668872cf11f167ac0462f8751/state +++ b/7d2d236668872cf11f167ac0462f8751/state @@ -1 +1 @@ -backlog \ No newline at end of file +inprogress \ No newline at end of file From 665f02cbe8fd04e381e40abe145ad5fd27f2afed Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 10:00:22 -0600 Subject: [PATCH 207/489] when changing state, include old state in git log message Before this commit: change state of issue f3990ac13cd93a925f2a66e6a72eb0f2 to backlog After this commit: change state of issue 406e2330695040fed5fdbcaae5d2b331, new -> inprogress --- src/issue.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/issue.rs b/src/issue.rs index 15d97f0..b309cae 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -251,6 +251,7 @@ impl Issue { /// Change the State of the Issue. pub fn set_state(&mut self, new_state: State) -> Result<(), IssueError> { + let old_state = self.state.clone(); let mut state_filename = std::path::PathBuf::from(&self.dir); state_filename.push("state"); let mut state_file = std::fs::File::create(&state_filename)?; @@ -260,8 +261,9 @@ impl Issue { crate::git::commit( &self.dir, &format!( - "change state of issue {} to {}", + "change state of issue {}, {} -> {}", self.dir.file_name().unwrap().to_string_lossy(), + old_state, new_state, ), )?; From d32c659e0b7cfc4b476b2acc0dc5944d78c1bb5a Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 10:06:27 -0600 Subject: [PATCH 208/489] edit description of issue 7d2d236668872cf11f167ac0462f8751 --- 7d2d236668872cf11f167ac0462f8751/description | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/7d2d236668872cf11f167ac0462f8751/description b/7d2d236668872cf11f167ac0462f8751/description index 488a279..1bfe147 100644 --- a/7d2d236668872cf11f167ac0462f8751/description +++ b/7d2d236668872cf11f167ac0462f8751/description @@ -1,13 +1,19 @@ -add `ent tag ISSUE_ID TAG` +add `ent tag ISSUE_ID [-]TAG` -Tags are short text strings without white space. +If the optional "-" is not supplied, the TAG is *added* to the ISSUE. + +If the "-" *is* supplied before the TAG, the TAG is *removed* from +the ISSUE. + +Tags are short text strings without white space, and not beginning with +"-". The Issue directory will gain a file named `tags`, containing the sorted list of tags. The `ent list` output will add a bit to the end of each issue displayed, something like "[tag1, tag2]", or nothing if the issue has no tags -(like how we don't show assignee if there is none). +(like how we don't show assignee if it is None). As part of this issue, the `ent list FILTER` will gain a new filter chunk type like "tags=v2.31,ux-papercut". From e0624d9dc8c0973595634a8b9f882aad535933af Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 10:33:12 -0600 Subject: [PATCH 209/489] edit description of issue 7d2d236668872cf11f167ac0462f8751 --- 7d2d236668872cf11f167ac0462f8751/description | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/7d2d236668872cf11f167ac0462f8751/description b/7d2d236668872cf11f167ac0462f8751/description index 1bfe147..cce35df 100644 --- a/7d2d236668872cf11f167ac0462f8751/description +++ b/7d2d236668872cf11f167ac0462f8751/description @@ -1,9 +1,15 @@ -add `ent tag ISSUE_ID [-]TAG` +add `ent tag ISSUE [[-]TAG]` -If the optional "-" is not supplied, the TAG is *added* to the ISSUE. +# Usage -If the "-" *is* supplied before the TAG, the TAG is *removed* from -the ISSUE. +`ent tag ISSUE`: List tags on ISSUE. + +`ent tag ISSUE TAG`: Add TAG to ISSUE. + +`ent tag ISSUE -TAG`: Remove TAG from ISSUE. + + +# Notes Tags are short text strings without white space, and not beginning with "-". From b02807eaca831bdf9e43e27699d28c108147460b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 10:18:46 -0600 Subject: [PATCH 210/489] Issue: add tags field --- src/issue.rs | 17 +++++++++++++++++ src/issues.rs | 11 +++++++++++ .../tags | 3 +++ 3 files changed, 31 insertions(+) create mode 100644 test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/tags diff --git a/src/issue.rs b/src/issue.rs index b309cae..52c2f0b 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -22,6 +22,7 @@ pub type IssueHandle = String; pub struct Issue { pub author: String, pub timestamp: chrono::DateTime, + pub tags: Vec, pub state: State, pub dependencies: Option>, pub assignee: Option, @@ -97,6 +98,7 @@ impl Issue { let mut dependencies: Option> = None; let mut comments = Vec::::new(); let mut assignee: Option = None; + let mut tags = Vec::::new(); for direntry in dir.read_dir()? { if let Ok(direntry) = direntry { @@ -119,6 +121,13 @@ impl Issue { if deps.len() > 0 { dependencies = Some(deps); } + } else if file_name == "tags" { + let contents = std::fs::read_to_string(direntry.path())?; + tags = contents + .lines() + .filter(|s| s.len() > 0) + .map(|tag| String::from(tag.trim())) + .collect(); } else if file_name == "comments" && direntry.metadata()?.is_dir() { Self::read_comments(&mut comments, &direntry.path())?; } else { @@ -138,6 +147,7 @@ impl Issue { Ok(Self { author, timestamp, + tags, state: state, dependencies, assignee, @@ -192,6 +202,7 @@ impl Issue { let mut issue = Self { author: String::from(""), timestamp: chrono::Local::now(), + tags: Vec::::new(), state: State::New, dependencies: None, assignee: None, @@ -372,6 +383,11 @@ mod tests { timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), + tags: Vec::::from([ + String::from("tag1"), + String::from("TAG2"), + String::from("i-am-also-a-tag") + ]), state: State::New, dependencies: None, assignee: None, @@ -391,6 +407,7 @@ mod tests { timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), + tags: Vec::::new(), state: State::InProgress, dependencies: None, assignee: Some(String::from("beep boop")), diff --git a/src/issues.rs b/src/issues.rs index 16dab55..5c314ac 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -100,6 +100,7 @@ mod tests { timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), + tags: Vec::::new(), state: crate::issue::State::InProgress, dependencies: None, assignee: Some(String::from("beep boop")), @@ -119,6 +120,11 @@ mod tests { timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), + tags: Vec::::from([ + String::from("tag1"), + String::from("TAG2"), + String::from("i-am-also-a-tag") + ]), state: crate::issue::State::New, dependencies: None, assignee: None, @@ -147,6 +153,7 @@ mod tests { timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") .unwrap() .with_timezone(&chrono::Local), + tags: Vec::::new(), state: crate::issue::State::Done, dependencies: None, assignee: None, @@ -180,6 +187,7 @@ mod tests { timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") .unwrap() .with_timezone(&chrono::Local), + tags: Vec::::new(), state: crate::issue::State::WontDo, dependencies: None, assignee: None, @@ -208,6 +216,7 @@ mod tests { timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() .with_timezone(&chrono::Local), + tags: Vec::::new(), state: crate::issue::State::Done, dependencies: None, assignee: None, @@ -227,6 +236,7 @@ mod tests { timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() .with_timezone(&chrono::Local), + tags: Vec::::new(), state: crate::issue::State::WontDo, dependencies: None, assignee: None, @@ -246,6 +256,7 @@ mod tests { timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() .with_timezone(&chrono::Local), + tags: Vec::::new(), state: crate::issue::State::WontDo, dependencies: Some(vec![ crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), diff --git a/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/tags b/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/tags new file mode 100644 index 0000000..04e82a6 --- /dev/null +++ b/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/tags @@ -0,0 +1,3 @@ +tag1 +TAG2 +i-am-also-a-tag From 4307fc8941324d20e50548b43a7baec7cbef2e5f Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 11:23:56 -0600 Subject: [PATCH 211/489] ent list: show tags --- src/bin/ent/main.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 655d16a..1d7b87a 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -207,7 +207,32 @@ fn handle_command( Some(assignee) => format!(" (👉 {})", assignee), None => String::from(""), }; - println!("{} {} {}{}", uuid, comments, issue.title(), assignee); + let tags = match &issue.tags.len() { + 0 => String::from(""), + _ => { + // 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. + let mut tags = String::from(" ["); + let mut separator = ""; + for tag in &issue.tags { + tags.push_str(separator); + tags.push_str(tag); + separator = ", "; + } + tags.push_str("]"); + tags + } + }; + println!( + "{} {} {}{}{}", + uuid, + comments, + issue.title(), + assignee, + tags + ); } println!(""); } From 2f1636db55f9a387772e3dd8350faa76581d7724 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 12:26:27 -0600 Subject: [PATCH 212/489] add Issue::add_tag() and Issue::remove_tag() --- src/issue.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/issue.rs b/src/issue.rs index 52c2f0b..8cdbd4f 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -52,6 +52,8 @@ pub enum IssueError { EditorError, #[error("supplied description is empty")] EmptyDescription, + #[error("tag {0} not found")] + TagNotFound(String), } impl FromStr for State { @@ -314,6 +316,37 @@ impl Issue { } Ok(()) } + + /// Add a new Tag to the Issue. Commits. + pub fn add_tag(&mut self, tag: &str) -> Result<(), IssueError> { + let tag_string = String::from(tag); + if self.tags.contains(&tag_string) { + return Ok(()); + } + self.tags.push(tag_string); + self.tags.sort(); + self.commit_tags(&format!( + "issue {} add tag {}", + self.dir.file_name().unwrap().to_string_lossy(), + tag + ))?; + Ok(()) + } + + /// Remove a Tag from the Issue. Commits. + pub fn remove_tag(&mut self, tag: &str) -> Result<(), IssueError> { + let tag_string = String::from(tag); + let Some(index) = self.tags.iter().position(|x| x == &tag_string) else { + return Err(IssueError::TagNotFound(tag_string)); + }; + self.tags.remove(index); + self.commit_tags(&format!( + "issue {} remove tag {}", + self.dir.file_name().unwrap().to_string_lossy(), + tag + ))?; + Ok(()) + } } // This is the internal/private API of Issue. @@ -368,6 +401,18 @@ impl Issue { self.read_description()?; Ok(()) } + + fn commit_tags(&self, commit_message: &str) -> Result<(), IssueError> { + let mut tags_filename = self.dir.clone(); + tags_filename.push("tags"); + let mut tags_file = std::fs::File::create(&tags_filename)?; + for tag in &self.tags { + writeln!(tags_file, "{}", tag)?; + } + crate::git::add(&tags_filename)?; + crate::git::commit(&self.dir, commit_message)?; + Ok(()) + } } #[cfg(test)] From 88025c5daead2f000a0733832ae2e3291bc50a6a Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 10:30:14 -0600 Subject: [PATCH 213/489] add `ent tag ISSUE [[-]TAG] --- src/bin/ent/main.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 1d7b87a..9c87234 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -64,6 +64,13 @@ enum Commands { issue_id: String, new_assignee: Option, }, + + /// Add or remove a Tag to/from an Issue, or list the Tags on an Issue. + Tag { + issue_id: String, + #[arg(allow_hyphen_values = true)] + tag: Option, + }, } /// The main function looks at the command-line arguments and determines @@ -430,6 +437,51 @@ fn handle_command( } } } + + Commands::Tag { issue_id, tag } => { + let issues = 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 = make_issues_database( + issues_database_source, + 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)?; + } + } + 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); + } + } + } + } + } + } } Ok(()) From 9970f3960d7c8752b80612e1a81318b1cda8c1f7 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 13:28:50 -0600 Subject: [PATCH 214/489] create new issue 46317396704f388542daa7cb56d50076 --- 46317396704f388542daa7cb56d50076/description | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 46317396704f388542daa7cb56d50076/description diff --git a/46317396704f388542daa7cb56d50076/description b/46317396704f388542daa7cb56d50076/description new file mode 100644 index 0000000..3b47fb6 --- /dev/null +++ b/46317396704f388542daa7cb56d50076/description @@ -0,0 +1,20 @@ +put ent issue database in a dir in the main branches? + +Currently entomologist can access an issue database in a branch (via +temporary worktrees), or in a directory (via direct access). + +The directory-based issue database mode is kind of broken, because +entomologist blindly runs `git commit` after modifying the database +(and isn't careful about managing the git index when doing so). + +(Tangent: The IssueDatabase and IssueDatabaseSource structs of the `ent` +binary should maybe move into the lib Issues struct, and the code could +make smarter choices about how to access the database.) + +Putting the entomologist issue database in the main branch(es) of the +repo has some appealing qualities. For example, a bug-fix branch could +both fix the bug in the code, and change the bug's issue status from +"InProgress" to "Done", so that when the branch is merged to main, +the bug gets fixed there. + +The branch can be rebased on top of main to pick up changes to the bug. From 943558637895423d9a32d95c6a64b43d98f45233 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 13:32:32 -0600 Subject: [PATCH 215/489] issue b738f2842db428df1b4aad0192a7f36c add tag docs --- b738f2842db428df1b4aad0192a7f36c/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 b738f2842db428df1b4aad0192a7f36c/tags diff --git a/b738f2842db428df1b4aad0192a7f36c/tags b/b738f2842db428df1b4aad0192a7f36c/tags new file mode 100644 index 0000000..d8f8d46 --- /dev/null +++ b/b738f2842db428df1b4aad0192a7f36c/tags @@ -0,0 +1 @@ +docs From 28db7669f4113fed431c4b4b9fa74d55e16e1ca0 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 14:24:20 -0600 Subject: [PATCH 216/489] ent: better `ent list --help` --- Cargo.toml | 2 +- src/bin/ent/main.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index af271d7..864691a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ log = ["dep:log", "dep:simple_logger"] [dependencies] anyhow = "1.0.95" chrono = "0.4.41" -clap = { version = "4.5.26", features = ["derive"] } +clap = { version = "4.5.26", features = ["derive", "wrap_help"] } log = { version = "0.4.27", optional = true } rand = "0.9.1" serde = { version = "1.0.217", features = ["derive"] } diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 9c87234..e09b1a9 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -25,6 +25,15 @@ enum Commands { /// List issues. List { /// Filter string, describes issues to include in the list. + /// The filter string is composed of chunks separated by ":". + /// Each chunk is of the form "name=condition". The supported + /// names and their matching conditions are: + /// + /// "state": Comma-separated list of states to list. + /// + /// "assignee": Comma-separated list of assignees to list. + /// Defaults to all assignees if not set. + /// #[arg(default_value_t = String::from("state=New,Backlog,Blocked,InProgress"))] filter: String, }, From 9d4409c00842a933d61b3a081b72c758e263c8dd Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 14:24:40 -0600 Subject: [PATCH 217/489] `ent list`: add filtering based on tags --- src/bin/ent/main.rs | 15 +++++++++++++++ src/issue.rs | 14 ++++++++++++++ src/lib.rs | 21 +++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index e09b1a9..d67ef3c 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -34,6 +34,10 @@ enum Commands { /// "assignee": Comma-separated list of assignees to list. /// Defaults to all assignees if not set. /// + /// "tag": Comma-separated list of tags to include or exclude + /// (if prefixed with "-"). If omitted, defaults to including + /// all tags and excluding none. + /// #[arg(default_value_t = String::from("state=New,Backlog,Blocked,InProgress"))] filter: String, }, @@ -187,6 +191,17 @@ fn handle_command( } } + if filter.include_tags.len() > 0 { + if !issue.has_any_tag(&filter.include_tags) { + continue; + } + } + if filter.exclude_tags.len() > 0 { + if issue.has_any_tag(&filter.exclude_tags) { + continue; + } + } + // This issue passed all the filters, include it in list. uuids_by_state .entry(issue.state.clone()) diff --git a/src/issue.rs b/src/issue.rs index 8cdbd4f..bd0632c 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -347,6 +347,20 @@ impl Issue { ))?; Ok(()) } + + pub fn has_tag(&self, tag: &str) -> bool { + let tag_string = String::from(tag); + self.tags.iter().position(|x| x == &tag_string).is_some() + } + + pub fn has_any_tag(&self, tags: &std::collections::HashSet<&str>) -> bool { + for tag in tags.iter() { + if self.has_tag(tag) { + return true; + } + } + return false; + } } // This is the internal/private API of Issue. diff --git a/src/lib.rs b/src/lib.rs index b28fb74..fa820b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,8 @@ pub enum ParseFilterError { pub struct Filter<'a> { pub include_states: std::collections::HashSet, pub include_assignees: std::collections::HashSet<&'a str>, + pub include_tags: std::collections::HashSet<&'a str>, + pub exclude_tags: std::collections::HashSet<&'a str>, } impl<'a> Filter<'a> { @@ -33,6 +35,8 @@ impl<'a> Filter<'a> { State::New, ]), include_assignees: std::collections::HashSet::<&'a str>::new(), + include_tags: std::collections::HashSet::<&'a str>::new(), + exclude_tags: std::collections::HashSet::<&'a str>::new(), }; for filter_chunk_str in filter_str.split(":") { @@ -48,12 +52,29 @@ impl<'a> Filter<'a> { f.include_states.insert(crate::issue::State::from_str(s)?); } } + "assignee" => { f.include_assignees.clear(); for s in tokens[1].split(",") { f.include_assignees.insert(s); } } + + "tag" => { + f.include_tags.clear(); + f.exclude_tags.clear(); + for s in tokens[1].split(",") { + if s.len() == 0 { + return Err(ParseFilterError::ParseError); + } + if s.chars().nth(0).unwrap() == '-' { + f.exclude_tags.insert(&s[1..]); + } else { + f.include_tags.insert(s); + } + } + } + _ => { println!("unknown filter chunk '{}'", filter_chunk_str); return Err(ParseFilterError::ParseError); From d56516af8348239bbc4782875e30267ae2eb056f Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 14:26:11 -0600 Subject: [PATCH 218/489] change state of issue 46317396704f388542daa7cb56d50076, new -> backlog --- 46317396704f388542daa7cb56d50076/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 46317396704f388542daa7cb56d50076/state diff --git a/46317396704f388542daa7cb56d50076/state b/46317396704f388542daa7cb56d50076/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/46317396704f388542daa7cb56d50076/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 3721483c2dbdd01ec6cbc6ba86ca84f8d7c2859a Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 14:48:04 -0600 Subject: [PATCH 219/489] git::sync(): more helpful error message when merge fails --- src/git.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/git.rs b/src/git.rs index 63cb004..fe0446a 100644 --- a/src/git.rs +++ b/src/git.rs @@ -251,7 +251,7 @@ pub fn sync(dir: &std::path::Path, remote: &str, branch: &str) -> Result<(), Git if !result.status.success() { println!( "Sync failed! 'git log' error! Help, a human needs to fix the mess in {:?}", - dir + branch ); println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); @@ -277,7 +277,7 @@ pub fn sync(dir: &std::path::Path, remote: &str, branch: &str) -> Result<(), Git if !result.status.success() { println!( "Sync failed! 'git log' error! Help, a human needs to fix the mess in {:?}", - dir + branch ); println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); @@ -297,7 +297,7 @@ pub fn sync(dir: &std::path::Path, remote: &str, branch: &str) -> Result<(), Git if !result.status.success() { println!( "Sync failed! Merge error! Help, a human needs to fix the mess in {:?}", - dir + branch ); println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); @@ -312,7 +312,7 @@ pub fn sync(dir: &std::path::Path, remote: &str, branch: &str) -> Result<(), Git if !result.status.success() { println!( "Sync failed! Push error! Help, a human needs to fix the mess in {:?}", - dir + branch ); println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); From 8d5105e4d5183e8cd3a826d69bd2369c8f218cc8 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 14:54:23 -0600 Subject: [PATCH 220/489] flesh out the README some --- README.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ca1a4e..71a2f8e 100644 --- a/README.md +++ b/README.md @@ -1 +1,81 @@ -This is a distributed, collaborative bug tracker, backed by git. +Entomologist is a distributed, collaborative, offline-first issue tracker, +backed by git. + + +# Quick start + +Entomologist provides a single executable called `ent` which performs +all interaction with the issues database. `ent --help` provides terse +usage info. + +No initialization is needed, just start using `ent` inside your git repo: + +``` +$ git clone git@server:my-repo.git +$ cd my-repo +$ ent list +# no issues shown, unless my-repo contained some already +``` + +Create an issue: +``` +$ ent new +# Starts your $EDITOR. Type in the issue description, "git-commit +# style" with a title line, optionally followed by an empty line and +# free form text. +``` + +List issues with `ent list`. Optionally takes a filter argument that +controls which issues are shown, see `ent list --help` for details. +For example, to show only new and backlog issues assigned to me or +unassigned, run `ent list state=new,backlog:assignee=$(whoami),`. + +Show all details of an issue with `ent show`. + +Modify the state of an issue using `ent state`. Supported states are New, +Backlog, InProgress, Done, and WontDo. + +Assign an issue to a person using `ent assign`. The person is just +a free-form text field for now. Make it a name, or an email address, +or whatever you want. + +Add a comment on an issue with `ent comment`. + +Edit an issue or a comment with `ent edit`. + +Add or remove tags on an issue using `ent tag`. + + +# Synchronization + +Synchronize your local issue database with the server using `ent sync`. +This will: + +1. Fetch the remote issue database branch into your local repo. + +2. Show the list of local changes not yet on the remote. + +3. Show the list of remote changes not yet incorporated into the local + branch. + +4. Merge the branches. + +5. Push the result back to the remote. + +Step 4 might fail if (for example) both sides edited the same issue in +a way that git can't merge automatically. In this case, check out the +`entomologist-data` branch, merge by hand and resolve the conflicts, +and run `ent sync` again. + + +# Git storage + +Issues are stored in a normal orphan branch in a git repo, next to but +independent of whatever else is stored in the repo. The default branch +name is `entomologist-data`. + +Anyone who has a clone of the repo has the complete issue database. + +Anyone who has write-access to the repo can modify the issue database. +The issue database branch can be modified by pull request, same as any +other branch. From e5fd166bffa3fb0d9ceb85186d050b2ce2278fd3 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 15:05:37 -0600 Subject: [PATCH 221/489] create new issue 4e314a8590864fa76d22758e1785ae35 --- 4e314a8590864fa76d22758e1785ae35/description | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 4e314a8590864fa76d22758e1785ae35/description diff --git a/4e314a8590864fa76d22758e1785ae35/description b/4e314a8590864fa76d22758e1785ae35/description new file mode 100644 index 0000000..9687fdf --- /dev/null +++ b/4e314a8590864fa76d22758e1785ae35/description @@ -0,0 +1,8 @@ +don't spawn an editor if stdin & stdout aren't a terminal + +I sometimes run `ent show ISSUE | more` because ISSUE doesn't fit on my +screen and we don't have built-in pager support yet. + +Then i see something i want to change, or i want to add a comment, so +i up-arrow in the shell and change `show` to `edit` or `comment`, and +ent opens what is in effect `vi | more` which isn't what i want at all. From 7c55fa4dc95767efb1cb4c2b69872df5903ee8ab Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 15:17:27 -0600 Subject: [PATCH 222/489] add comment bd8d26478108fde4e1a9f32507957e5f on issue 08f0d7ee7842c439382816d21ec1dea2 --- .../description | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 08f0d7ee7842c439382816d21ec1dea2/comments/bd8d26478108fde4e1a9f32507957e5f/description diff --git a/08f0d7ee7842c439382816d21ec1dea2/comments/bd8d26478108fde4e1a9f32507957e5f/description b/08f0d7ee7842c439382816d21ec1dea2/comments/bd8d26478108fde4e1a9f32507957e5f/description new file mode 100644 index 0000000..a1b03ce --- /dev/null +++ b/08f0d7ee7842c439382816d21ec1dea2/comments/bd8d26478108fde4e1a9f32507957e5f/description @@ -0,0 +1,17 @@ +I'm liking the idea of a per-issue key/value store. + +I think keys and values are both strings, with possible further +parsing/interpretation for some keys? + +For the application in this issue we'd teach ent to create a "finished" +key, maybe when setting the state to Done, with a value of an +ISO8601-style datetime stamp (e.g. "2025-07-12T15:08:58-06:00"). + +There would be a documented spec of reserved keys and their special +meanings and handling. + +Full user access via CLI, maybe something like `ent get VARIABLE` and +`ent set VARIABLE VALUE`? Or `ent variable [NAME[=VALUE]]`? + +Or maybe merge this with the tags somehow? Would that be simpler, +or more complicated (for the user)? From 0d1d23f1dda78b16275493d5aa91780c6e3fb0c2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 16:13:44 -0600 Subject: [PATCH 223/489] create new issue 8ef792a2092e5ba84da3cb4efc3c88c6 --- 8ef792a2092e5ba84da3cb4efc3c88c6/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 8ef792a2092e5ba84da3cb4efc3c88c6/description diff --git a/8ef792a2092e5ba84da3cb4efc3c88c6/description b/8ef792a2092e5ba84da3cb4efc3c88c6/description new file mode 100644 index 0000000..b39a250 --- /dev/null +++ b/8ef792a2092e5ba84da3cb4efc3c88c6/description @@ -0,0 +1 @@ +teach `ent show` to include tags \ No newline at end of file From b62b421d122f8080ff4e90b9989c421121c162e4 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 16:14:08 -0600 Subject: [PATCH 224/489] change state of issue 8ef792a2092e5ba84da3cb4efc3c88c6, new -> backlog --- 8ef792a2092e5ba84da3cb4efc3c88c6/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 8ef792a2092e5ba84da3cb4efc3c88c6/state diff --git a/8ef792a2092e5ba84da3cb4efc3c88c6/state b/8ef792a2092e5ba84da3cb4efc3c88c6/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/8ef792a2092e5ba84da3cb4efc3c88c6/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 3e72093bb19ed721e7bb82735b3407cbed0636cf Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 16:14:16 -0600 Subject: [PATCH 225/489] change state of issue 4e314a8590864fa76d22758e1785ae35, new -> backlog --- 4e314a8590864fa76d22758e1785ae35/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 4e314a8590864fa76d22758e1785ae35/state diff --git a/4e314a8590864fa76d22758e1785ae35/state b/4e314a8590864fa76d22758e1785ae35/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/4e314a8590864fa76d22758e1785ae35/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 398709848be356cdeb13d049b5566c3fecfaeac2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 16:14:46 -0600 Subject: [PATCH 226/489] change state of issue 7d2d236668872cf11f167ac0462f8751, inprogress -> done --- 7d2d236668872cf11f167ac0462f8751/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7d2d236668872cf11f167ac0462f8751/state b/7d2d236668872cf11f167ac0462f8751/state index 505c028..348ebd9 100644 --- a/7d2d236668872cf11f167ac0462f8751/state +++ b/7d2d236668872cf11f167ac0462f8751/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 3894121f64ee9e35608a9846e6e8cd01151022de Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 16:15:39 -0600 Subject: [PATCH 227/489] change assignee of issue 4e314a8590864fa76d22758e1785ae35, None -> seb --- 4e314a8590864fa76d22758e1785ae35/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 4e314a8590864fa76d22758e1785ae35/assignee diff --git a/4e314a8590864fa76d22758e1785ae35/assignee b/4e314a8590864fa76d22758e1785ae35/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/4e314a8590864fa76d22758e1785ae35/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From a2770aaf8bbc3b8bc8fd9a8a03ec14dd52d045a9 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 12 Jul 2025 16:15:44 -0600 Subject: [PATCH 228/489] change state of issue 4e314a8590864fa76d22758e1785ae35, backlog -> inprogress --- 4e314a8590864fa76d22758e1785ae35/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/4e314a8590864fa76d22758e1785ae35/state b/4e314a8590864fa76d22758e1785ae35/state index b6fe829..505c028 100644 --- a/4e314a8590864fa76d22758e1785ae35/state +++ b/4e314a8590864fa76d22758e1785ae35/state @@ -1 +1 @@ -backlog \ No newline at end of file +inprogress \ No newline at end of file From 490f946ef6bca53f965514235d1325f6a5c4cab6 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 13 Jul 2025 10:38:29 -0600 Subject: [PATCH 229/489] don't open an editor is stdin or stdout is not a terminal --- src/comment.rs | 8 +++++++- src/issue.rs | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/comment.rs b/src/comment.rs index e9c3134..c8e26c9 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -1,4 +1,4 @@ -use std::io::Write; +use std::io::{IsTerminal, Write}; #[derive(Debug, PartialEq)] pub struct Comment { @@ -26,6 +26,8 @@ pub enum CommentError { EditorError, #[error("supplied description is empty")] EmptyDescription, + #[error("stdin/stdout is not a terminal")] + StdioIsNotTerminal, } impl Comment { @@ -146,6 +148,10 @@ impl Comment { /// Used by Issue::add_comment() when no description is supplied, /// and (FIXME: in the future) used by `ent edit COMMENT`. pub fn edit_description_file(&mut self) -> Result<(), CommentError> { + if !std::io::stdin().is_terminal() || !std::io::stdout().is_terminal() { + return Err(CommentError::StdioIsNotTerminal); + } + let description_filename = self.description_filename(); let exists = description_filename.exists(); diff --git a/src/issue.rs b/src/issue.rs index bd0632c..1558578 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -1,5 +1,5 @@ use core::fmt; -use std::io::Write; +use std::io::{IsTerminal, Write}; use std::str::FromStr; #[cfg(feature = "log")] @@ -54,6 +54,8 @@ pub enum IssueError { EmptyDescription, #[error("tag {0} not found")] TagNotFound(String), + #[error("stdin/stdout is not a terminal")] + StdioIsNotTerminal, } impl FromStr for State { @@ -385,6 +387,10 @@ impl Issue { /// Used by Issue::new() when no description is supplied, and also /// used by `ent edit ISSUE`. fn edit_description_file(&mut self) -> Result<(), IssueError> { + if !std::io::stdin().is_terminal() || !std::io::stdout().is_terminal() { + return Err(IssueError::StdioIsNotTerminal); + } + let description_filename = self.description_filename(); let exists = description_filename.exists(); let editor = match std::env::var("EDITOR") { From f3df4f7f138f6aa248747756379aa5df58a37a84 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 13 Jul 2025 10:39:17 -0600 Subject: [PATCH 230/489] change state of issue 4e314a8590864fa76d22758e1785ae35, inprogress -> done --- 4e314a8590864fa76d22758e1785ae35/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/4e314a8590864fa76d22758e1785ae35/state b/4e314a8590864fa76d22758e1785ae35/state index 505c028..348ebd9 100644 --- a/4e314a8590864fa76d22758e1785ae35/state +++ b/4e314a8590864fa76d22758e1785ae35/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 1477ac0dbb9ee4e5a53529ae5522df93852b99c3 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 13 Jul 2025 10:40:50 -0600 Subject: [PATCH 231/489] change assignee of issue 08f0d7ee7842c439382816d21ec1dea2, None -> seb --- 08f0d7ee7842c439382816d21ec1dea2/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 08f0d7ee7842c439382816d21ec1dea2/assignee diff --git a/08f0d7ee7842c439382816d21ec1dea2/assignee b/08f0d7ee7842c439382816d21ec1dea2/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/08f0d7ee7842c439382816d21ec1dea2/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From a7d68c39748cb6107565c3e1f401929d543dd2fe Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 13 Jul 2025 10:40:55 -0600 Subject: [PATCH 232/489] change state of issue 08f0d7ee7842c439382816d21ec1dea2, backlog -> inprogress --- 08f0d7ee7842c439382816d21ec1dea2/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/08f0d7ee7842c439382816d21ec1dea2/state b/08f0d7ee7842c439382816d21ec1dea2/state index b6fe829..505c028 100644 --- a/08f0d7ee7842c439382816d21ec1dea2/state +++ b/08f0d7ee7842c439382816d21ec1dea2/state @@ -1 +1 @@ -backlog \ No newline at end of file +inprogress \ No newline at end of file From 9132f9cdb139b983485d829d77db2c96f4e63f24 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 13 Jul 2025 13:22:07 -0600 Subject: [PATCH 233/489] edit description of issue 08f0d7ee7842c439382816d21ec1dea2 --- 08f0d7ee7842c439382816d21ec1dea2/description | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/08f0d7ee7842c439382816d21ec1dea2/description b/08f0d7ee7842c439382816d21ec1dea2/description index 203c6c6..c99db9c 100644 --- a/08f0d7ee7842c439382816d21ec1dea2/description +++ b/08f0d7ee7842c439382816d21ec1dea2/description @@ -65,8 +65,7 @@ position the tags on the commits i want? Seems cumbersome. entomologist doesn't have due dates yet, but we should probably add that. -entomologist doesn't have tags yet (there's an open ticket for it), -but once we have that it'll be easy to list "tags=selected,interrupt". +entomologist has tags so it's be easy to list "tags=selected,interrupt". Or maybe we should just use "state=inprogress"? From 1a6fbdb17edef3e440266798ada1cc2d96b2113d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 13 Jul 2025 13:54:00 -0600 Subject: [PATCH 234/489] create new issue d3a705245bd69aa56524b80b5ae0bc26 --- d3a705245bd69aa56524b80b5ae0bc26/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 d3a705245bd69aa56524b80b5ae0bc26/description diff --git a/d3a705245bd69aa56524b80b5ae0bc26/description b/d3a705245bd69aa56524b80b5ae0bc26/description new file mode 100644 index 0000000..c3e3187 --- /dev/null +++ b/d3a705245bd69aa56524b80b5ae0bc26/description @@ -0,0 +1 @@ +move issues database type to issues.rs \ No newline at end of file From a23a69f36bc041ba648643aac5563128e23024cc Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 13 Jul 2025 13:58:56 -0600 Subject: [PATCH 235/489] edit description of issue d3a705245bd69aa56524b80b5ae0bc26 --- d3a705245bd69aa56524b80b5ae0bc26/description | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/d3a705245bd69aa56524b80b5ae0bc26/description b/d3a705245bd69aa56524b80b5ae0bc26/description index c3e3187..0df3299 100644 --- a/d3a705245bd69aa56524b80b5ae0bc26/description +++ b/d3a705245bd69aa56524b80b5ae0bc26/description @@ -1 +1,5 @@ -move issues database type to issues.rs \ No newline at end of file +move IssuesDatabase out of binary and into library + +currently the IssuesDatabase type lives in the binary application, which makes it impossible to use with other applications. + +since the IssuesDatabase type is needed for some of the git management related to the storage of issues, it should exist as part of the library From d2df1bc1e84667c1e379568935e5448b23cb4788 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 13 Jul 2025 14:06:04 -0600 Subject: [PATCH 236/489] move entomologist database to new library file fixes: d3a705245bd69aa56524b80b5ae0bc26 --- src/bin/ent/main.rs | 119 ++++++++------------------------------------ src/database.rs | 82 ++++++++++++++++++++++++++++++ src/lib.rs | 1 + 3 files changed, 103 insertions(+), 99 deletions(-) create mode 100644 src/database.rs diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index d67ef3c..b211383 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -86,92 +86,13 @@ enum Commands { }, } -/// The main function looks at the command-line arguments and determines -/// from there where to get the Issues Database to operate on. -/// -/// * If the user specified `--issues-dir` we use that. -/// -/// * If the user specified `--issues-branch` we make sure the branch -/// exists, then use that. -/// -/// * If the user specified neither, we use the default branch -/// `entomologist-data` (after ensuring that it exists). -/// -/// * If the user specified both, it's an operator error and we abort. -/// -/// The result of that code populates an IssuesDatabaseSource object, -/// that gets used later to access the database. -enum IssuesDatabaseSource<'a> { - Dir(&'a std::path::Path), - Branch(&'a str), -} - -/// The IssuesDatabase type is a "fat path". It holds a PathBuf pointing -/// at the issues database directory, and optionally a Worktree object -/// corresponding to that path. -/// -/// The worktree field itself is never read: we put its path in `dir` -/// and that's all that the calling code cares about. -/// -/// The Worktree object is included here *when* the IssuesDatabaseSource -/// is a branch. In this case a git worktree is created to hold the -/// checkout of the branch. When the IssueDatabase object is dropped, -/// the contained/owned Worktree object is dropped, which deletes the -/// worktree directory from the filesystem and prunes the worktree from -/// git's worktree list. -struct IssuesDatabase { - dir: std::path::PathBuf, - - #[allow(dead_code)] - worktree: Option, -} - -enum IssuesDatabaseAccess { - ReadOnly, - ReadWrite, -} - -fn make_issues_database( - issues_database_source: &IssuesDatabaseSource, - access_type: IssuesDatabaseAccess, -) -> anyhow::Result { - match issues_database_source { - IssuesDatabaseSource::Dir(dir) => Ok(IssuesDatabase { - dir: std::path::PathBuf::from(dir), - worktree: None, - }), - IssuesDatabaseSource::Branch(branch) => { - let worktree = match access_type { - IssuesDatabaseAccess::ReadOnly => { - entomologist::git::Worktree::new_detached(branch)? - } - IssuesDatabaseAccess::ReadWrite => entomologist::git::Worktree::new(branch)?, - }; - Ok(IssuesDatabase { - dir: std::path::PathBuf::from(worktree.path()), - worktree: Some(worktree), - }) - } - } -} - -fn read_issues_database( - issues_database_source: &IssuesDatabaseSource, -) -> anyhow::Result { - let issues_database = - make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadOnly)?; - Ok(entomologist::issues::Issues::new_from_dir( - &issues_database.dir, - )?) -} - fn handle_command( args: &Args, - issues_database_source: &IssuesDatabaseSource, + issues_database_source: &entomologist::database::IssuesDatabaseSource, ) -> anyhow::Result<()> { match &args.command { Commands::List { filter } => { - let issues = read_issues_database(issues_database_source)?; + let issues = entomologist::database::read_issues_database(issues_database_source)?; let filter = entomologist::Filter::new_from_str(filter)?; let mut uuids_by_state = std::collections::HashMap::< entomologist::issue::State, @@ -271,7 +192,7 @@ fn handle_command( Commands::New { description } => { let issues_database = - make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadWrite)?; + entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; match entomologist::issue::Issue::new(&issues_database.dir, description) { Err(entomologist::issue::IssueError::EmptyDescription) => { println!("no new issue created"); @@ -289,7 +210,7 @@ fn handle_command( Commands::Edit { uuid } => { let issues_database = - make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadWrite)?; + entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; if let Some(issue) = issues.get_mut_issue(uuid) { match issue.edit_description() { @@ -323,7 +244,7 @@ fn handle_command( } Commands::Show { issue_id } => { - let issues = read_issues_database(issues_database_source)?; + let issues = entomologist::database::read_issues_database(issues_database_source)?; match issues.get_issue(issue_id) { Some(issue) => { println!("issue {}", issue_id); @@ -359,7 +280,7 @@ fn handle_command( } => match new_state { Some(new_state) => { let issues_database = - make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadWrite)?; + entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; match issues.issues.get_mut(issue_id) { Some(issue) => { @@ -374,7 +295,7 @@ fn handle_command( } } None => { - let issues = read_issues_database(issues_database_source)?; + let issues = entomologist::database::read_issues_database(issues_database_source)?; match issues.issues.get(issue_id) { Some(issue) => { println!("issue: {}", issue_id); @@ -392,7 +313,7 @@ fn handle_command( description, } => { let issues_database = - make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadWrite)?; + 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)); @@ -417,9 +338,9 @@ fn handle_command( } Commands::Sync { remote } => { - if let IssuesDatabaseSource::Branch(branch) = issues_database_source { + if let entomologist::database::IssuesDatabaseSource::Branch(branch) = issues_database_source { let issues_database = - make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadWrite)?; + entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; entomologist::git::sync(&issues_database.dir, remote, branch)?; println!("synced {:?} with {:?}", branch, remote); } else { @@ -433,7 +354,7 @@ fn handle_command( issue_id, new_assignee, } => { - let issues = read_issues_database(issues_database_source)?; + 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)); }; @@ -444,9 +365,9 @@ fn handle_command( println!("issue: {}", issue_id); match new_assignee { Some(new_assignee) => { - let issues_database = make_issues_database( + let issues_database = entomologist::database::make_issues_database( issues_database_source, - IssuesDatabaseAccess::ReadWrite, + entomologist::database::IssuesDatabaseAccess::ReadWrite, )?; let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; @@ -463,16 +384,16 @@ fn handle_command( } Commands::Tag { issue_id, tag } => { - let issues = read_issues_database(issues_database_source)?; + 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 = make_issues_database( + let issues_database = entomologist::database::make_issues_database( issues_database_source, - IssuesDatabaseAccess::ReadWrite, + entomologist::database::IssuesDatabaseAccess::ReadWrite, )?; let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; @@ -519,9 +440,9 @@ fn main() -> anyhow::Result<()> { // println!("{:?}", args); let issues_database_source = match (&args.issues_dir, &args.issues_branch) { - (Some(dir), None) => IssuesDatabaseSource::Dir(std::path::Path::new(dir)), - (None, Some(branch)) => IssuesDatabaseSource::Branch(branch), - (None, None) => IssuesDatabaseSource::Branch("entomologist-data"), + (Some(dir), None) => entomologist::database::IssuesDatabaseSource::Dir(std::path::Path::new(dir)), + (None, Some(branch)) => entomologist::database::IssuesDatabaseSource::Branch(branch), + (None, None) => entomologist::database::IssuesDatabaseSource::Branch("entomologist-data"), (Some(_), Some(_)) => { return Err(anyhow::anyhow!( "don't specify both `--issues-dir` and `--issues-branch`" @@ -529,7 +450,7 @@ fn main() -> anyhow::Result<()> { } }; - if let IssuesDatabaseSource::Branch(branch) = &issues_database_source { + if let entomologist::database::IssuesDatabaseSource::Branch(branch) = &issues_database_source { if !entomologist::git::git_branch_exists(branch)? { entomologist::git::create_orphan_branch(branch)?; } diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..06e8e1b --- /dev/null +++ b/src/database.rs @@ -0,0 +1,82 @@ + +/// The main function looks at the command-line arguments and determines +/// from there where to get the Issues Database to operate on. +/// +/// * If the user specified `--issues-dir` we use that. +/// +/// * If the user specified `--issues-branch` we make sure the branch +/// exists, then use that. +/// +/// * If the user specified neither, we use the default branch +/// `entomologist-data` (after ensuring that it exists). +/// +/// * If the user specified both, it's an operator error and we abort. +/// +/// The result of that code populates an IssuesDatabaseSource object, +/// that gets used later to access the database. +pub enum IssuesDatabaseSource<'a> { + Dir(&'a std::path::Path), + Branch(&'a str), +} + + + +/// The IssuesDatabase type is a "fat path". It holds a PathBuf pointing +/// at the issues database directory, and optionally a Worktree object +/// corresponding to that path. +/// +/// The worktree field itself is never read: we put its path in `dir` +/// and that's all that the calling code cares about. +/// +/// The Worktree object is included here *when* the IssuesDatabaseSource +/// is a branch. In this case a git worktree is created to hold the +/// checkout of the branch. When the IssueDatabase object is dropped, +/// the contained/owned Worktree object is dropped, which deletes the +/// worktree directory from the filesystem and prunes the worktree from +/// git's worktree list. + +pub struct IssuesDatabase { + pub dir: std::path::PathBuf, + + #[allow(dead_code)] + pub worktree: Option, +} + +pub enum IssuesDatabaseAccess { + ReadOnly, + ReadWrite, +} + +pub fn make_issues_database( + issues_database_source: &IssuesDatabaseSource, + access_type: IssuesDatabaseAccess, +) -> anyhow::Result { + match issues_database_source { + IssuesDatabaseSource::Dir(dir) => Ok(IssuesDatabase { + dir: std::path::PathBuf::from(dir), + worktree: None, + }), + IssuesDatabaseSource::Branch(branch) => { + let worktree = match access_type { + IssuesDatabaseAccess::ReadOnly => { + crate::git::Worktree::new_detached(branch)? + } + IssuesDatabaseAccess::ReadWrite => crate::git::Worktree::new(branch)?, + }; + Ok(IssuesDatabase { + dir: std::path::PathBuf::from(worktree.path()), + worktree: Some(worktree), + }) + } + } +} + +pub fn read_issues_database( + issues_database_source: &IssuesDatabaseSource, +) -> anyhow::Result { + let issues_database = + make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadOnly)?; + Ok(crate::issues::Issues::new_from_dir( + &issues_database.dir, + )?) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index fa820b4..17104ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod comment; pub mod git; pub mod issue; pub mod issues; +pub mod database; #[derive(Debug, thiserror::Error)] pub enum ParseFilterError { From 64de226c58ed2a6cfb5026be39c8ef6cec9b7b75 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 13 Jul 2025 14:08:15 -0600 Subject: [PATCH 237/489] add comment 273273aa8822e304c60ddc160b2fae54 on issue d3a705245bd69aa56524b80b5ae0bc26 --- .../comments/273273aa8822e304c60ddc160b2fae54/description | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 d3a705245bd69aa56524b80b5ae0bc26/comments/273273aa8822e304c60ddc160b2fae54/description diff --git a/d3a705245bd69aa56524b80b5ae0bc26/comments/273273aa8822e304c60ddc160b2fae54/description b/d3a705245bd69aa56524b80b5ae0bc26/comments/273273aa8822e304c60ddc160b2fae54/description new file mode 100644 index 0000000..ba5b521 --- /dev/null +++ b/d3a705245bd69aa56524b80b5ae0bc26/comments/273273aa8822e304c60ddc160b2fae54/description @@ -0,0 +1,2 @@ +opened new MR: +https://git.glyphs.tech/taproot-manufacturing/entomologist/compare/main...03/issues-db-into-library From 2b9aa6bb95901b424117ada4c00033eefed33960 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 13 Jul 2025 14:08:48 -0600 Subject: [PATCH 238/489] change assignee of issue d3a705245bd69aa56524b80b5ae0bc26, None -> sigil-03 --- d3a705245bd69aa56524b80b5ae0bc26/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 d3a705245bd69aa56524b80b5ae0bc26/assignee diff --git a/d3a705245bd69aa56524b80b5ae0bc26/assignee b/d3a705245bd69aa56524b80b5ae0bc26/assignee new file mode 100644 index 0000000..284bb3b --- /dev/null +++ b/d3a705245bd69aa56524b80b5ae0bc26/assignee @@ -0,0 +1 @@ +sigil-03 \ No newline at end of file From 4b8dc28a55a73a936e92c6301e38328ee8dc16d2 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 13 Jul 2025 14:09:05 -0600 Subject: [PATCH 239/489] change state of issue d3a705245bd69aa56524b80b5ae0bc26, new -> inprogress --- d3a705245bd69aa56524b80b5ae0bc26/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 d3a705245bd69aa56524b80b5ae0bc26/state diff --git a/d3a705245bd69aa56524b80b5ae0bc26/state b/d3a705245bd69aa56524b80b5ae0bc26/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/d3a705245bd69aa56524b80b5ae0bc26/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 61df7ede8f77ccf58f9e75a8b1d74a468125ec7e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 13 Jul 2025 14:21:28 -0600 Subject: [PATCH 240/489] add better error handling --- src/database.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/database.rs b/src/database.rs index 06e8e1b..8afcd8d 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,3 +1,15 @@ +use thiserror::Error; +use crate::{git::GitError, issues::ReadIssuesError}; + +/// Errors that the DB can emit: +#[derive(Debug, Error)] +pub enum Error { + #[error(transparent)] + IssuesError(#[from] ReadIssuesError), + #[error(transparent)] + GitError(#[from] GitError), +} + /// The main function looks at the command-line arguments and determines /// from there where to get the Issues Database to operate on. @@ -50,7 +62,7 @@ pub enum IssuesDatabaseAccess { pub fn make_issues_database( issues_database_source: &IssuesDatabaseSource, access_type: IssuesDatabaseAccess, -) -> anyhow::Result { +) -> Result { match issues_database_source { IssuesDatabaseSource::Dir(dir) => Ok(IssuesDatabase { dir: std::path::PathBuf::from(dir), @@ -73,7 +85,7 @@ pub fn make_issues_database( pub fn read_issues_database( issues_database_source: &IssuesDatabaseSource, -) -> anyhow::Result { +) -> Result { let issues_database = make_issues_database(issues_database_source, IssuesDatabaseAccess::ReadOnly)?; Ok(crate::issues::Issues::new_from_dir( From e20ed74861e42c4cc889d9ca84211e56fc15bf5b Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 13 Jul 2025 21:38:53 -0600 Subject: [PATCH 241/489] create new issue 5e1a860b3ab12ee297492d70d68711d8 --- 5e1a860b3ab12ee297492d70d68711d8/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 5e1a860b3ab12ee297492d70d68711d8/description diff --git a/5e1a860b3ab12ee297492d70d68711d8/description b/5e1a860b3ab12ee297492d70d68711d8/description new file mode 100644 index 0000000..37a2c21 --- /dev/null +++ b/5e1a860b3ab12ee297492d70d68711d8/description @@ -0,0 +1,3 @@ +add TUI binary + +build a TUI binary for ent which mirrors the CLI functionality From ca306c8a794a4a6e8fb6c94f924d2159ce8cb016 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 13 Jul 2025 21:40:59 -0600 Subject: [PATCH 242/489] change assignee of issue 5e1a860b3ab12ee297492d70d68711d8, None -> sigil-03 --- 5e1a860b3ab12ee297492d70d68711d8/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 5e1a860b3ab12ee297492d70d68711d8/assignee diff --git a/5e1a860b3ab12ee297492d70d68711d8/assignee b/5e1a860b3ab12ee297492d70d68711d8/assignee new file mode 100644 index 0000000..284bb3b --- /dev/null +++ b/5e1a860b3ab12ee297492d70d68711d8/assignee @@ -0,0 +1 @@ +sigil-03 \ No newline at end of file From a4a027567181189317de99f7106f258496b012b8 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 13 Jul 2025 21:41:11 -0600 Subject: [PATCH 243/489] change state of issue 5e1a860b3ab12ee297492d70d68711d8, new -> inprogress --- 5e1a860b3ab12ee297492d70d68711d8/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 5e1a860b3ab12ee297492d70d68711d8/state diff --git a/5e1a860b3ab12ee297492d70d68711d8/state b/5e1a860b3ab12ee297492d70d68711d8/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/5e1a860b3ab12ee297492d70d68711d8/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 6d71ecbba01e49d7282a9f67f0612201350d1a4b Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 13 Jul 2025 21:44:40 -0600 Subject: [PATCH 244/489] add comment 4e8ba061e3643ed2be8d1f448badfbc3 on issue 5e1a860b3ab12ee297492d70d68711d8 --- .../comments/4e8ba061e3643ed2be8d1f448badfbc3/description | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 5e1a860b3ab12ee297492d70d68711d8/comments/4e8ba061e3643ed2be8d1f448badfbc3/description diff --git a/5e1a860b3ab12ee297492d70d68711d8/comments/4e8ba061e3643ed2be8d1f448badfbc3/description b/5e1a860b3ab12ee297492d70d68711d8/comments/4e8ba061e3643ed2be8d1f448badfbc3/description new file mode 100644 index 0000000..21acc15 --- /dev/null +++ b/5e1a860b3ab12ee297492d70d68711d8/comments/4e8ba061e3643ed2be8d1f448badfbc3/description @@ -0,0 +1,5 @@ +have initial rough copy of the TUI binary. i had initially started developing it as a standalone application but then decided it would be better to include it as part of the entomologist repository. + +currently the TUI can only list issues and show a preview, and scaling is broken. + +i also need to update it so that the widget implementations are done as part of the library instead of using the horrible, cursed wrappers that are currently around the types... (since it was a seperate binary, and the trait was from a different crate and the type was from a different crate, rust won't let you `impl` a trait) From 44802c8b4bc0fcd16399a5224f17b8d645904ada Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 13 Jul 2025 21:56:29 -0600 Subject: [PATCH 245/489] change state of issue d3a705245bd69aa56524b80b5ae0bc26, inprogress -> done --- d3a705245bd69aa56524b80b5ae0bc26/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/d3a705245bd69aa56524b80b5ae0bc26/state b/d3a705245bd69aa56524b80b5ae0bc26/state index 505c028..348ebd9 100644 --- a/d3a705245bd69aa56524b80b5ae0bc26/state +++ b/d3a705245bd69aa56524b80b5ae0bc26/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 052a4d5263c3b170f5bbda24bbef6dab3075a2d9 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:00:29 -0600 Subject: [PATCH 246/489] edit description of issue 5e1a860b3ab12ee297492d70d68711d8 --- 5e1a860b3ab12ee297492d70d68711d8/description | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/5e1a860b3ab12ee297492d70d68711d8/description b/5e1a860b3ab12ee297492d70d68711d8/description index 37a2c21..b789456 100644 --- a/5e1a860b3ab12ee297492d70d68711d8/description +++ b/5e1a860b3ab12ee297492d70d68711d8/description @@ -1,3 +1,13 @@ -add TUI binary +TUI v0.1 build a TUI binary for ent which mirrors the CLI functionality + +- [ ] issues list + - [ ] filtering(?) +- [x] show issue preview +- [x] show issue detail +- [ ] commands + - [ ] new ($EDITOR) + - [ ] edit ($EDITOR) + - [ ] comment ($EDITOR) + - [ ] state From 1a2d5c86345af814e0f90273fd3ec26ece284f26 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:01:13 -0600 Subject: [PATCH 247/489] create new issue 88d5111fd6e59802d0b839ff1fd6bf71 --- 88d5111fd6e59802d0b839ff1fd6bf71/description | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 88d5111fd6e59802d0b839ff1fd6bf71/description diff --git a/88d5111fd6e59802d0b839ff1fd6bf71/description b/88d5111fd6e59802d0b839ff1fd6bf71/description new file mode 100644 index 0000000..58ef8ac --- /dev/null +++ b/88d5111fd6e59802d0b839ff1fd6bf71/description @@ -0,0 +1,9 @@ +issues list + +add issues list to TUI + +REQ: +- [ ] list issues + +STRETCH: +- [ ] filter issues From 656a687b1c083e564c6a293a08eb45d407b5a5d5 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:01:45 -0600 Subject: [PATCH 248/489] issue 5e1a860b3ab12ee297492d70d68711d8 add tag tui/v0.1 --- 5e1a860b3ab12ee297492d70d68711d8/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 5e1a860b3ab12ee297492d70d68711d8/tags diff --git a/5e1a860b3ab12ee297492d70d68711d8/tags b/5e1a860b3ab12ee297492d70d68711d8/tags new file mode 100644 index 0000000..6795335 --- /dev/null +++ b/5e1a860b3ab12ee297492d70d68711d8/tags @@ -0,0 +1 @@ +tui/v0.1 From b3ce6ad13a6d3669f70c88fe5089a32edf3c289f Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:02:15 -0600 Subject: [PATCH 249/489] issue 88d5111fd6e59802d0b839ff1fd6bf71 add tag tui/v0.1 --- 88d5111fd6e59802d0b839ff1fd6bf71/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 88d5111fd6e59802d0b839ff1fd6bf71/tags diff --git a/88d5111fd6e59802d0b839ff1fd6bf71/tags b/88d5111fd6e59802d0b839ff1fd6bf71/tags new file mode 100644 index 0000000..6795335 --- /dev/null +++ b/88d5111fd6e59802d0b839ff1fd6bf71/tags @@ -0,0 +1 @@ +tui/v0.1 From 38469ab4350e1e43864fb265efc5085f6191e8db Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:04:44 -0600 Subject: [PATCH 250/489] edit description of issue 5e1a860b3ab12ee297492d70d68711d8 --- 5e1a860b3ab12ee297492d70d68711d8/description | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5e1a860b3ab12ee297492d70d68711d8/description b/5e1a860b3ab12ee297492d70d68711d8/description index b789456..074aef1 100644 --- a/5e1a860b3ab12ee297492d70d68711d8/description +++ b/5e1a860b3ab12ee297492d70d68711d8/description @@ -2,7 +2,7 @@ TUI v0.1 build a TUI binary for ent which mirrors the CLI functionality -- [ ] issues list +- [ ] issues list (88d5111fd6e59802d0b839ff1fd6bf71) - [ ] filtering(?) - [x] show issue preview - [x] show issue detail From bc13712091a051fe5e7fe80de867161005631edb Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:06:59 -0600 Subject: [PATCH 251/489] create new issue bd00a62f9d7c77fd8dd0da5d20aa803d --- bd00a62f9d7c77fd8dd0da5d20aa803d/description | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 bd00a62f9d7c77fd8dd0da5d20aa803d/description diff --git a/bd00a62f9d7c77fd8dd0da5d20aa803d/description b/bd00a62f9d7c77fd8dd0da5d20aa803d/description new file mode 100644 index 0000000..b46e862 --- /dev/null +++ b/bd00a62f9d7c77fd8dd0da5d20aa803d/description @@ -0,0 +1,6 @@ +issue preview + +show a preview of the issue: +- [ ] author +- [ ] ID +- [ ] description preview From 91490016b321b86582e199fe42d2f74de4c99442 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:07:27 -0600 Subject: [PATCH 252/489] edit description of issue bd00a62f9d7c77fd8dd0da5d20aa803d --- bd00a62f9d7c77fd8dd0da5d20aa803d/description | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bd00a62f9d7c77fd8dd0da5d20aa803d/description b/bd00a62f9d7c77fd8dd0da5d20aa803d/description index b46e862..e4159f6 100644 --- a/bd00a62f9d7c77fd8dd0da5d20aa803d/description +++ b/bd00a62f9d7c77fd8dd0da5d20aa803d/description @@ -1,4 +1,4 @@ -issue preview +issue preview show a preview of the issue: - [ ] author From 8b36b616fb51e8785af901867d3cad5d5a602c23 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:07:34 -0600 Subject: [PATCH 253/489] issue bd00a62f9d7c77fd8dd0da5d20aa803d add tag tui/v0.1 --- bd00a62f9d7c77fd8dd0da5d20aa803d/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 bd00a62f9d7c77fd8dd0da5d20aa803d/tags diff --git a/bd00a62f9d7c77fd8dd0da5d20aa803d/tags b/bd00a62f9d7c77fd8dd0da5d20aa803d/tags new file mode 100644 index 0000000..6795335 --- /dev/null +++ b/bd00a62f9d7c77fd8dd0da5d20aa803d/tags @@ -0,0 +1 @@ +tui/v0.1 From 5c82892f21022922aa87fbd59924e05997b8b169 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:08:01 -0600 Subject: [PATCH 254/489] change assignee of issue bd00a62f9d7c77fd8dd0da5d20aa803d, None -> sigil-03 --- bd00a62f9d7c77fd8dd0da5d20aa803d/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 bd00a62f9d7c77fd8dd0da5d20aa803d/assignee diff --git a/bd00a62f9d7c77fd8dd0da5d20aa803d/assignee b/bd00a62f9d7c77fd8dd0da5d20aa803d/assignee new file mode 100644 index 0000000..284bb3b --- /dev/null +++ b/bd00a62f9d7c77fd8dd0da5d20aa803d/assignee @@ -0,0 +1 @@ +sigil-03 \ No newline at end of file From 256c5d0340cc92c9368afd5e065199e7ab1314b1 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:08:40 -0600 Subject: [PATCH 255/489] change assignee of issue 88d5111fd6e59802d0b839ff1fd6bf71, None -> sigil-03 --- 88d5111fd6e59802d0b839ff1fd6bf71/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 88d5111fd6e59802d0b839ff1fd6bf71/assignee diff --git a/88d5111fd6e59802d0b839ff1fd6bf71/assignee b/88d5111fd6e59802d0b839ff1fd6bf71/assignee new file mode 100644 index 0000000..284bb3b --- /dev/null +++ b/88d5111fd6e59802d0b839ff1fd6bf71/assignee @@ -0,0 +1 @@ +sigil-03 \ No newline at end of file From a5f2b0230622b1203bd1e2ca487cb4bedd1d012e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:08:58 -0600 Subject: [PATCH 256/489] change state of issue bd00a62f9d7c77fd8dd0da5d20aa803d, new -> inprogress --- bd00a62f9d7c77fd8dd0da5d20aa803d/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 bd00a62f9d7c77fd8dd0da5d20aa803d/state diff --git a/bd00a62f9d7c77fd8dd0da5d20aa803d/state b/bd00a62f9d7c77fd8dd0da5d20aa803d/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/bd00a62f9d7c77fd8dd0da5d20aa803d/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 86a57cd67de7f2a5b4f5c1db70040bff3a880b3d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:09:41 -0600 Subject: [PATCH 257/489] change state of issue 88d5111fd6e59802d0b839ff1fd6bf71, new -> inprogress --- 88d5111fd6e59802d0b839ff1fd6bf71/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 88d5111fd6e59802d0b839ff1fd6bf71/state diff --git a/88d5111fd6e59802d0b839ff1fd6bf71/state b/88d5111fd6e59802d0b839ff1fd6bf71/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/88d5111fd6e59802d0b839ff1fd6bf71/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 320c60924e8620f10de923e77a50ab9e85e31828 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:11:01 -0600 Subject: [PATCH 258/489] edit description of issue 5e1a860b3ab12ee297492d70d68711d8 --- 5e1a860b3ab12ee297492d70d68711d8/description | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/5e1a860b3ab12ee297492d70d68711d8/description b/5e1a860b3ab12ee297492d70d68711d8/description index 074aef1..b0c38ae 100644 --- a/5e1a860b3ab12ee297492d70d68711d8/description +++ b/5e1a860b3ab12ee297492d70d68711d8/description @@ -4,8 +4,9 @@ build a TUI binary for ent which mirrors the CLI functionality - [ ] issues list (88d5111fd6e59802d0b839ff1fd6bf71) - [ ] filtering(?) -- [x] show issue preview -- [x] show issue detail +- [ ] show issue preview (bd00a62f9d7c77fd8dd0da5d20aa803d) +- [ ] show issue detail +- [ ] show issue comments - [ ] commands - [ ] new ($EDITOR) - [ ] edit ($EDITOR) From fcb5a5df358b6817f90e2c83d5bc47a4e01632b5 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:12:15 -0600 Subject: [PATCH 259/489] create new issue bdf44bd55fdecdfc1badcb7123d2a606 --- bdf44bd55fdecdfc1badcb7123d2a606/description | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 bdf44bd55fdecdfc1badcb7123d2a606/description diff --git a/bdf44bd55fdecdfc1badcb7123d2a606/description b/bdf44bd55fdecdfc1badcb7123d2a606/description new file mode 100644 index 0000000..738aa35 --- /dev/null +++ b/bdf44bd55fdecdfc1badcb7123d2a606/description @@ -0,0 +1,6 @@ +show issue detail + +show details about the issue +* potentially seperate screen? +* allow scrolling +* show comments From 109adf92184a291b8db2d24f7a96d8acd47b755b Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:12:41 -0600 Subject: [PATCH 260/489] issue bdf44bd55fdecdfc1badcb7123d2a606 add tag tui/v0.1 --- bdf44bd55fdecdfc1badcb7123d2a606/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 bdf44bd55fdecdfc1badcb7123d2a606/tags diff --git a/bdf44bd55fdecdfc1badcb7123d2a606/tags b/bdf44bd55fdecdfc1badcb7123d2a606/tags new file mode 100644 index 0000000..6795335 --- /dev/null +++ b/bdf44bd55fdecdfc1badcb7123d2a606/tags @@ -0,0 +1 @@ +tui/v0.1 From b2a078bd240c38f2bdb413595bed315249540d31 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:12:46 -0600 Subject: [PATCH 261/489] change assignee of issue bdf44bd55fdecdfc1badcb7123d2a606, None -> sigil-03 --- bdf44bd55fdecdfc1badcb7123d2a606/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 bdf44bd55fdecdfc1badcb7123d2a606/assignee diff --git a/bdf44bd55fdecdfc1badcb7123d2a606/assignee b/bdf44bd55fdecdfc1badcb7123d2a606/assignee new file mode 100644 index 0000000..284bb3b --- /dev/null +++ b/bdf44bd55fdecdfc1badcb7123d2a606/assignee @@ -0,0 +1 @@ +sigil-03 \ No newline at end of file From 2d5c673c7ecd6800e93dd4b5fb08f92d44ce496c Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:12:51 -0600 Subject: [PATCH 262/489] change state of issue bdf44bd55fdecdfc1badcb7123d2a606, new -> inprogress --- bdf44bd55fdecdfc1badcb7123d2a606/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 bdf44bd55fdecdfc1badcb7123d2a606/state diff --git a/bdf44bd55fdecdfc1badcb7123d2a606/state b/bdf44bd55fdecdfc1badcb7123d2a606/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/bdf44bd55fdecdfc1badcb7123d2a606/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From db9179fcfc7f29422f8098ba7e7ec1b0b2d1b6a6 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:15:03 -0600 Subject: [PATCH 263/489] create new issue cc6607ad7565902b149e9836dd4f029c --- cc6607ad7565902b149e9836dd4f029c/description | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 cc6607ad7565902b149e9836dd4f029c/description diff --git a/cc6607ad7565902b149e9836dd4f029c/description b/cc6607ad7565902b149e9836dd4f029c/description new file mode 100644 index 0000000..b44bbef --- /dev/null +++ b/cc6607ad7565902b149e9836dd4f029c/description @@ -0,0 +1,10 @@ +commands + +allow calling commands from the TUI. commands that open an editor will open the system editor and then return to the TUI when complete. + +- [ ] new +- [ ] edit +- [ ] commant +- [ ] state +- [ ] assign +- [ ] ...? From 6f8788112804e12bfff01b5acd6b70dca2f3bc80 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:15:15 -0600 Subject: [PATCH 264/489] change assignee of issue cc6607ad7565902b149e9836dd4f029c, None -> sigil-03 --- cc6607ad7565902b149e9836dd4f029c/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 cc6607ad7565902b149e9836dd4f029c/assignee diff --git a/cc6607ad7565902b149e9836dd4f029c/assignee b/cc6607ad7565902b149e9836dd4f029c/assignee new file mode 100644 index 0000000..284bb3b --- /dev/null +++ b/cc6607ad7565902b149e9836dd4f029c/assignee @@ -0,0 +1 @@ +sigil-03 \ No newline at end of file From a420da6f4214ac71a6c03f4c50c8367adb5a54cc Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:15:36 -0600 Subject: [PATCH 265/489] change state of issue cc6607ad7565902b149e9836dd4f029c, new -> backlog --- cc6607ad7565902b149e9836dd4f029c/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 cc6607ad7565902b149e9836dd4f029c/state diff --git a/cc6607ad7565902b149e9836dd4f029c/state b/cc6607ad7565902b149e9836dd4f029c/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/cc6607ad7565902b149e9836dd4f029c/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 5c5963bb43396e8d1f25537653e599f4dff364bb Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:15:48 -0600 Subject: [PATCH 266/489] issue cc6607ad7565902b149e9836dd4f029c add tag tui/v0.1 --- cc6607ad7565902b149e9836dd4f029c/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 cc6607ad7565902b149e9836dd4f029c/tags diff --git a/cc6607ad7565902b149e9836dd4f029c/tags b/cc6607ad7565902b149e9836dd4f029c/tags new file mode 100644 index 0000000..6795335 --- /dev/null +++ b/cc6607ad7565902b149e9836dd4f029c/tags @@ -0,0 +1 @@ +tui/v0.1 From 0c5475b7f3c6c70ae54bb788738ecca4b18ced63 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:17:56 -0600 Subject: [PATCH 267/489] create new issue f14bd410300d6d8802d873c6b584c4aa --- f14bd410300d6d8802d873c6b584c4aa/description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 f14bd410300d6d8802d873c6b584c4aa/description diff --git a/f14bd410300d6d8802d873c6b584c4aa/description b/f14bd410300d6d8802d873c6b584c4aa/description new file mode 100644 index 0000000..b35ecdd --- /dev/null +++ b/f14bd410300d6d8802d873c6b584c4aa/description @@ -0,0 +1,4 @@ +make `ent new` output issue ID + + +when the `ent new` command is invoked, it should also return the issue ID to STDOUT From 03c832304c909964b49e89dd10a220e6abe6bd3a Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:18:18 -0600 Subject: [PATCH 268/489] issue f14bd410300d6d8802d873c6b584c4aa add tag cli --- f14bd410300d6d8802d873c6b584c4aa/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 f14bd410300d6d8802d873c6b584c4aa/tags diff --git a/f14bd410300d6d8802d873c6b584c4aa/tags b/f14bd410300d6d8802d873c6b584c4aa/tags new file mode 100644 index 0000000..573c0c4 --- /dev/null +++ b/f14bd410300d6d8802d873c6b584c4aa/tags @@ -0,0 +1 @@ +cli From eb313bed7b282a4516e9be7d21691ae5e728d0bf Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:19:47 -0600 Subject: [PATCH 269/489] create new issue 7e2a3a59fb6b77403ff1035255367607 --- 7e2a3a59fb6b77403ff1035255367607/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 7e2a3a59fb6b77403ff1035255367607/description diff --git a/7e2a3a59fb6b77403ff1035255367607/description b/7e2a3a59fb6b77403ff1035255367607/description new file mode 100644 index 0000000..c778f10 --- /dev/null +++ b/7e2a3a59fb6b77403ff1035255367607/description @@ -0,0 +1,3 @@ +allow adding / removing dependencies from CLI + +allow user to add / remove dependencies from the CLI From af19e7a0cd2e3346d8a2d4f896190a19aecc4f57 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:20:01 -0600 Subject: [PATCH 270/489] issue 7e2a3a59fb6b77403ff1035255367607 add tag cli --- 7e2a3a59fb6b77403ff1035255367607/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 7e2a3a59fb6b77403ff1035255367607/tags diff --git a/7e2a3a59fb6b77403ff1035255367607/tags b/7e2a3a59fb6b77403ff1035255367607/tags new file mode 100644 index 0000000..573c0c4 --- /dev/null +++ b/7e2a3a59fb6b77403ff1035255367607/tags @@ -0,0 +1 @@ +cli From b9b1354c6c8fba322ca7e36cfad953b4f416fc02 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:20:27 -0600 Subject: [PATCH 271/489] edit description of issue bdf44bd55fdecdfc1badcb7123d2a606 --- bdf44bd55fdecdfc1badcb7123d2a606/description | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdf44bd55fdecdfc1badcb7123d2a606/description b/bdf44bd55fdecdfc1badcb7123d2a606/description index 738aa35..33e41d0 100644 --- a/bdf44bd55fdecdfc1badcb7123d2a606/description +++ b/bdf44bd55fdecdfc1badcb7123d2a606/description @@ -1,4 +1,4 @@ -show issue detail +issue detail show details about the issue * potentially seperate screen? From a9d16ec90ed6522562fb8057b195d243b7333ed1 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 14 Jul 2025 23:20:46 -0600 Subject: [PATCH 272/489] edit description of issue bd00a62f9d7c77fd8dd0da5d20aa803d --- bd00a62f9d7c77fd8dd0da5d20aa803d/description | 1 + 1 file changed, 1 insertion(+) diff --git a/bd00a62f9d7c77fd8dd0da5d20aa803d/description b/bd00a62f9d7c77fd8dd0da5d20aa803d/description index e4159f6..509ca4b 100644 --- a/bd00a62f9d7c77fd8dd0da5d20aa803d/description +++ b/bd00a62f9d7c77fd8dd0da5d20aa803d/description @@ -3,4 +3,5 @@ issue preview show a preview of the issue: - [ ] author - [ ] ID +- [ ] tags - [ ] description preview From fdcb2dea1a90362b55cfa4454d60bd87b68b0512 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 15 Jul 2025 09:55:26 -0600 Subject: [PATCH 273/489] change state of issue d3a705245bd69aa56524b80b5ae0bc26, inprogress -> done --- d3a705245bd69aa56524b80b5ae0bc26/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/d3a705245bd69aa56524b80b5ae0bc26/state b/d3a705245bd69aa56524b80b5ae0bc26/state index 505c028..348ebd9 100644 --- a/d3a705245bd69aa56524b80b5ae0bc26/state +++ b/d3a705245bd69aa56524b80b5ae0bc26/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 0c453a9923d187107b2153a90673fa4f7cd7ab6d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 15 Jul 2025 09:55:59 -0600 Subject: [PATCH 274/489] change state of issue f14bd410300d6d8802d873c6b584c4aa, new -> inprogress --- f14bd410300d6d8802d873c6b584c4aa/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 f14bd410300d6d8802d873c6b584c4aa/state diff --git a/f14bd410300d6d8802d873c6b584c4aa/state b/f14bd410300d6d8802d873c6b584c4aa/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/f14bd410300d6d8802d873c6b584c4aa/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From e6a678bb9728ae234fa070677c88abeba06c6899 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 15 Jul 2025 09:56:06 -0600 Subject: [PATCH 275/489] change assignee of issue f14bd410300d6d8802d873c6b584c4aa, None -> sigil-03 --- f14bd410300d6d8802d873c6b584c4aa/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 f14bd410300d6d8802d873c6b584c4aa/assignee diff --git a/f14bd410300d6d8802d873c6b584c4aa/assignee b/f14bd410300d6d8802d873c6b584c4aa/assignee new file mode 100644 index 0000000..284bb3b --- /dev/null +++ b/f14bd410300d6d8802d873c6b584c4aa/assignee @@ -0,0 +1 @@ +sigil-03 \ No newline at end of file From f6f1f093e01a548d0f2a51866af0e811e5df1b3e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 15 Jul 2025 10:11:54 -0600 Subject: [PATCH 276/489] create new issue 3b35e0324d3e5bf863ef93c2f64a6add --- 3b35e0324d3e5bf863ef93c2f64a6add/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 3b35e0324d3e5bf863ef93c2f64a6add/description diff --git a/3b35e0324d3e5bf863ef93c2f64a6add/description b/3b35e0324d3e5bf863ef93c2f64a6add/description new file mode 100644 index 0000000..28e1169 --- /dev/null +++ b/3b35e0324d3e5bf863ef93c2f64a6add/description @@ -0,0 +1,3 @@ +include issue ID in the issue + +currently the issue ID is not contained by the issue data structure. update the issue data structure so it contains the issue ID. From d748e8607566263f6039573cc65633c7505e4b14 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 15 Jul 2025 10:12:10 -0600 Subject: [PATCH 277/489] change assignee of issue 3b35e0324d3e5bf863ef93c2f64a6add, None -> sigil-03 --- 3b35e0324d3e5bf863ef93c2f64a6add/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 3b35e0324d3e5bf863ef93c2f64a6add/assignee diff --git a/3b35e0324d3e5bf863ef93c2f64a6add/assignee b/3b35e0324d3e5bf863ef93c2f64a6add/assignee new file mode 100644 index 0000000..284bb3b --- /dev/null +++ b/3b35e0324d3e5bf863ef93c2f64a6add/assignee @@ -0,0 +1 @@ +sigil-03 \ No newline at end of file From dcd90381d5b801d6ea8ae5ffad18479dc721b7cc Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 15 Jul 2025 10:12:16 -0600 Subject: [PATCH 278/489] change state of issue 3b35e0324d3e5bf863ef93c2f64a6add, new -> inprogress --- 3b35e0324d3e5bf863ef93c2f64a6add/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 3b35e0324d3e5bf863ef93c2f64a6add/state diff --git a/3b35e0324d3e5bf863ef93c2f64a6add/state b/3b35e0324d3e5bf863ef93c2f64a6add/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/3b35e0324d3e5bf863ef93c2f64a6add/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From ca2919d30a9ed141b38b14e06a4cbc69f4961075 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 15 Jul 2025 10:26:43 -0600 Subject: [PATCH 279/489] create new issue 0f8b0c982bcfe7d5406bea58301014bc --- 0f8b0c982bcfe7d5406bea58301014bc/description | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 0f8b0c982bcfe7d5406bea58301014bc/description diff --git a/0f8b0c982bcfe7d5406bea58301014bc/description b/0f8b0c982bcfe7d5406bea58301014bc/description new file mode 100644 index 0000000..c3e0ce5 --- /dev/null +++ b/0f8b0c982bcfe7d5406bea58301014bc/description @@ -0,0 +1,17 @@ +we need some way to search the text of issues and comments + +Is this a normal filter? Maybe `ent list text=REGEX`? + +Or maybe we want more detailed output than just a list? Showing the +actual Issue or Comment that matches. Maybe `ent search REGEX` producing +something like: +``` +$ ent search REGEX +issue: issue1 +blah blah + +issue: issue2 +blah + comment 1 + comment 2 +``` From b4752106afd0b604955a57720095b68cdc6b67e6 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 15 Jul 2025 10:32:48 -0600 Subject: [PATCH 280/489] add comment 89e04463c36f5cb8d82b66995541c1ce on issue 08f0d7ee7842c439382816d21ec1dea2 --- .../comments/89e04463c36f5cb8d82b66995541c1ce/description | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 08f0d7ee7842c439382816d21ec1dea2/comments/89e04463c36f5cb8d82b66995541c1ce/description diff --git a/08f0d7ee7842c439382816d21ec1dea2/comments/89e04463c36f5cb8d82b66995541c1ce/description b/08f0d7ee7842c439382816d21ec1dea2/comments/89e04463c36f5cb8d82b66995541c1ce/description new file mode 100644 index 0000000..f2c0eb7 --- /dev/null +++ b/08f0d7ee7842c439382816d21ec1dea2/comments/89e04463c36f5cb8d82b66995541c1ce/description @@ -0,0 +1,5 @@ +Or maybe things that required special handling in the code should live +directly as fields in the Issue struct? With their own getter/setter +API & CLI? + +Not sure what's cleaner. From b57585f31579eccdb004c1f938dc25c903a96b69 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 15 Jul 2025 10:43:12 -0600 Subject: [PATCH 281/489] add comment 76eee0c6f69981b5c698208725bdd689 on issue 7e2a3a59fb6b77403ff1035255367607 --- .../76eee0c6f69981b5c698208725bdd689/description | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 7e2a3a59fb6b77403ff1035255367607/comments/76eee0c6f69981b5c698208725bdd689/description diff --git a/7e2a3a59fb6b77403ff1035255367607/comments/76eee0c6f69981b5c698208725bdd689/description b/7e2a3a59fb6b77403ff1035255367607/comments/76eee0c6f69981b5c698208725bdd689/description new file mode 100644 index 0000000..2a2a658 --- /dev/null +++ b/7e2a3a59fb6b77403ff1035255367607/comments/76eee0c6f69981b5c698208725bdd689/description @@ -0,0 +1,12 @@ +We also need to implement dependency handling, not sure how that should work. + +* If we add a dependency to an Issue, to we put the Issue into state + Blocked? + +* If an Issue is marked Done, do we search all the other issues to see + if the Done issue was a dependency, and remove it? + +* What happens when the last dependency of an Issue is removed? + +(Maybe this comment should be a new issue, not a comment on the parent +issue) From e8220d50a4d7050f704201d64ee16ca8cf73d152 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 15 Jul 2025 10:47:43 -0600 Subject: [PATCH 282/489] create new issue dd20d3ddc86ee802fe7b15e2c91dc160 --- dd20d3ddc86ee802fe7b15e2c91dc160/description | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 dd20d3ddc86ee802fe7b15e2c91dc160/description diff --git a/dd20d3ddc86ee802fe7b15e2c91dc160/description b/dd20d3ddc86ee802fe7b15e2c91dc160/description new file mode 100644 index 0000000..0e51f85 --- /dev/null +++ b/dd20d3ddc86ee802fe7b15e2c91dc160/description @@ -0,0 +1,10 @@ +store tags as a dir of files, not a file of lines + +Currently we store all tags in a file (named `tags`) in the Issue dir. +The `tags` file has a line per tag. + +This is bad because `ent sync` will get a git merge conflict if we add +or remove a tag near where someone else added or removed a tag. + +Better to have a `tags` a directory, with an empty file per tag. +The filename is the tag. This use is conflict free in git. From f131f1f5da4ec6ebe98e1b77e5b38e623b3f1318 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 15 Jul 2025 10:47:58 -0600 Subject: [PATCH 283/489] change state of issue dd20d3ddc86ee802fe7b15e2c91dc160, new -> inprogress --- dd20d3ddc86ee802fe7b15e2c91dc160/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 dd20d3ddc86ee802fe7b15e2c91dc160/state diff --git a/dd20d3ddc86ee802fe7b15e2c91dc160/state b/dd20d3ddc86ee802fe7b15e2c91dc160/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/dd20d3ddc86ee802fe7b15e2c91dc160/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From f77babb1a4c6e94302eee4ce9a5c222c71868d95 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 15 Jul 2025 10:48:04 -0600 Subject: [PATCH 284/489] change assignee of issue dd20d3ddc86ee802fe7b15e2c91dc160, None -> seb --- dd20d3ddc86ee802fe7b15e2c91dc160/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 dd20d3ddc86ee802fe7b15e2c91dc160/assignee diff --git a/dd20d3ddc86ee802fe7b15e2c91dc160/assignee b/dd20d3ddc86ee802fe7b15e2c91dc160/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/dd20d3ddc86ee802fe7b15e2c91dc160/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From 733100fefb4f250b36d84716f72572e098aad711 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 15 Jul 2025 10:53:52 -0600 Subject: [PATCH 285/489] add the ID field back into the Issue struct --- src/issue.rs | 23 ++++++++++- src/issues.rs | 110 ++++++++++++++++++++++---------------------------- 2 files changed, 70 insertions(+), 63 deletions(-) diff --git a/src/issue.rs b/src/issue.rs index 1558578..0de413c 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -20,6 +20,7 @@ pub type IssueHandle = String; #[derive(Debug, PartialEq)] pub struct Issue { + pub id: String, pub author: String, pub timestamp: chrono::DateTime, pub tags: Vec, @@ -56,6 +57,8 @@ pub enum IssueError { TagNotFound(String), #[error("stdin/stdout is not a terminal")] StdioIsNotTerminal, + #[error("Failed to parse issue ID")] + IdError, } impl FromStr for State { @@ -145,10 +148,21 @@ impl Issue { return Err(IssueError::IssueParseError); } + // parse the issue ID from the directory name + let id = if let Some(parsed_id) = match dir.file_name() { + Some(name) => name.to_str(), + None => Err(IssueError::IdError)?, + } { + String::from(parsed_id) + } else { + Err(IssueError::IdError)? + }; + let author = crate::git::git_log_oldest_author(dir)?; let timestamp = crate::git::git_log_oldest_timestamp(dir)?; Ok(Self { + id, author, timestamp, tags, @@ -204,6 +218,7 @@ impl Issue { std::fs::create_dir(&issue_dir)?; let mut issue = Self { + id: String::from(&issue_id), author: String::from(""), timestamp: chrono::Local::now(), tags: Vec::::new(), @@ -444,6 +459,7 @@ mod tests { let issue_dir = std::path::Path::new("test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/"); let issue = Issue::new_from_dir(issue_dir).unwrap(); let expected = Issue { + id: String::from("3943fc5c173fdf41c0a22251593cd476d96e6c9f"), author: String::from("Sebastian Kuzminsky "), timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() @@ -451,12 +467,14 @@ mod tests { tags: Vec::::from([ String::from("tag1"), String::from("TAG2"), - String::from("i-am-also-a-tag") + String::from("i-am-also-a-tag"), ]), state: State::New, dependencies: None, assignee: None, - description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), + description: String::from( + "this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n", + ), comments: Vec::::new(), dir: std::path::PathBuf::from(issue_dir), }; @@ -468,6 +486,7 @@ mod tests { let issue_dir = std::path::Path::new("test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/"); let issue = Issue::new_from_dir(issue_dir).unwrap(); let expected = Issue { + id: String::from("7792b063eef6d33e7da5dc1856750c149ba678c6"), author: String::from("Sebastian Kuzminsky "), timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() diff --git a/src/issues.rs b/src/issues.rs index 5c314ac..42e964d 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -31,8 +31,8 @@ impl Issues { } } - pub fn add_issue(&mut self, uuid: String, issue: crate::issue::Issue) { - self.issues.insert(uuid, issue); + pub fn add_issue(&mut self, issue: crate::issue::Issue) { + self.issues.insert(issue.id.clone(), issue); } pub fn get_issue(&self, issue_id: &str) -> Option<&crate::issue::Issue> { @@ -56,14 +56,8 @@ impl Issues { for direntry in dir.read_dir()? { if let Ok(direntry) = direntry { if direntry.metadata()?.is_dir() { - let uuid = match direntry.file_name().into_string() { - Ok(uuid) => uuid, - Err(orig_string) => { - return Err(ReadIssuesError::FilenameError(orig_string)) - } - }; let issue = crate::issue::Issue::new_from_dir(direntry.path().as_path())?; - issues.add_issue(uuid, issue); + issues.add_issue(issue); } else if direntry.file_name() == "config.toml" { issues.parse_config(direntry.path().as_path())?; } else { @@ -93,29 +87,27 @@ mod tests { let uuid = String::from("7792b063eef6d33e7da5dc1856750c149ba678c6"); let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); - expected.add_issue( - uuid, - crate::issue::Issue { - author: String::from("Sebastian Kuzminsky "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") - .unwrap() - .with_timezone(&chrono::Local), - tags: Vec::::new(), - state: crate::issue::State::InProgress, - dependencies: None, - assignee: Some(String::from("beep boop")), - description: String::from("minimal"), - comments: Vec::::new(), - dir, - }, - ); + expected.add_issue(crate::issue::Issue { + id: uuid, + author: String::from("Sebastian Kuzminsky "), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + .unwrap() + .with_timezone(&chrono::Local), + tags: Vec::::new(), + state: crate::issue::State::InProgress, + dependencies: None, + assignee: Some(String::from("beep boop")), + description: String::from("minimal"), + comments: Vec::::new(), + dir, + }); let uuid = String::from("3943fc5c173fdf41c0a22251593cd476d96e6c9f"); let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); expected.add_issue( - uuid, crate::issue::Issue { + id: uuid, author: String::from("Sebastian Kuzminsky "), timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() @@ -146,22 +138,20 @@ mod tests { let uuid = String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"); let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); - expected.add_issue( - uuid, - crate::issue::Issue { - author: String::from("Sebastian Kuzminsky "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") - .unwrap() - .with_timezone(&chrono::Local), - tags: Vec::::new(), - state: crate::issue::State::Done, - dependencies: None, - assignee: None, - description: String::from("oh yeah we got titles"), - comments: Vec::::new(), - dir, - }, - ); + expected.add_issue(crate::issue::Issue { + id: uuid, + author: String::from("Sebastian Kuzminsky "), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") + .unwrap() + .with_timezone(&chrono::Local), + tags: Vec::::new(), + state: crate::issue::State::Done, + dependencies: None, + assignee: None, + description: String::from("oh yeah we got titles"), + comments: Vec::::new(), + dir, + }); let uuid = String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"); let mut dir = std::path::PathBuf::from(issues_dir); @@ -181,8 +171,8 @@ mod tests { } ); expected.add_issue( - uuid, crate::issue::Issue { + id: uuid, author: String::from("Sebastian Kuzminsky "), timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") .unwrap() @@ -209,29 +199,27 @@ mod tests { let uuid = String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"); let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); - expected.add_issue( - uuid, - crate::issue::Issue { - author: String::from("sigil-03 "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") - .unwrap() - .with_timezone(&chrono::Local), - tags: Vec::::new(), - state: crate::issue::State::Done, - dependencies: None, - assignee: None, - description: String::from("oh yeah we got titles\n"), - comments: Vec::::new(), - dir, - }, - ); + expected.add_issue(crate::issue::Issue { + id: uuid, + author: String::from("sigil-03 "), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") + .unwrap() + .with_timezone(&chrono::Local), + tags: Vec::::new(), + state: crate::issue::State::Done, + dependencies: None, + assignee: None, + description: String::from("oh yeah we got titles\n"), + comments: Vec::::new(), + dir, + }); let uuid = String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"); let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); expected.add_issue( - uuid, crate::issue::Issue { + id: uuid, author: String::from("sigil-03 "), timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() @@ -250,8 +238,8 @@ mod tests { let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); expected.add_issue( - uuid, crate::issue::Issue { + id: uuid, author: String::from("sigil-03 "), timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() From 502e843b2f7449c2914050c2299725585a649e8d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 15 Jul 2025 11:09:06 -0600 Subject: [PATCH 286/489] add comment 3eb43ce8ab338850fb6e5005ab5fee8d on issue 08f0d7ee7842c439382816d21ec1dea2 --- .../description | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 08f0d7ee7842c439382816d21ec1dea2/comments/3eb43ce8ab338850fb6e5005ab5fee8d/description diff --git a/08f0d7ee7842c439382816d21ec1dea2/comments/3eb43ce8ab338850fb6e5005ab5fee8d/description b/08f0d7ee7842c439382816d21ec1dea2/comments/3eb43ce8ab338850fb6e5005ab5fee8d/description new file mode 100644 index 0000000..fc4ece5 --- /dev/null +++ b/08f0d7ee7842c439382816d21ec1dea2/comments/3eb43ce8ab338850fb6e5005ab5fee8d/description @@ -0,0 +1,26 @@ +maybe there's a delineation that's starting to be drawn here between an "issue" and what the database on the backend uses. + +for the filesystem DB, i think it might make sense to have a hashmap that stores everything as a key-value pair, where each key is a file, and each value is the contents of that file. + +once we go up the stack, i think it makes sense to have things in concrete structs, since that's easier to reason about. + +this also frees up the filesystem DB to get used for other things potentially, not just issues. + +so you'd have a system where + +``` +filesystem +^ +| +v +db layer: key/value pair +^ +| +v +application layer: concrete structs (like `Issue` etc) +^ +| +v +presentation layer: (CLI / TUI / etc.) +``` + From 612afacbbb7730d3fac1803c9d252f10ff001852 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 15 Jul 2025 14:53:08 -0600 Subject: [PATCH 287/489] add comment b725410a5c0ff4bd65fc242f760cc0d4 on issue 08f0d7ee7842c439382816d21ec1dea2 --- .../description | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 08f0d7ee7842c439382816d21ec1dea2/comments/b725410a5c0ff4bd65fc242f760cc0d4/description diff --git a/08f0d7ee7842c439382816d21ec1dea2/comments/b725410a5c0ff4bd65fc242f760cc0d4/description b/08f0d7ee7842c439382816d21ec1dea2/comments/b725410a5c0ff4bd65fc242f760cc0d4/description new file mode 100644 index 0000000..808e094 --- /dev/null +++ b/08f0d7ee7842c439382816d21ec1dea2/comments/b725410a5c0ff4bd65fc242f760cc0d4/description @@ -0,0 +1,44 @@ +I like the idea of a layered architecture, with a db layer that manages +filesystem i/o. + + +# API + +* Read a filesystem object into a struct Issue (e.g. `ent show`) + +* Write a whole struct Issue into a filesystem object (e.g. `ent new` + or `ent comment`). + +* Write a single field from a struct Issue into an existing filesystem + object (e.g. `ent state ISSUE Done`). + +On write operations, the git commit message should be meaningful to +the application. Maybe that can be done generically by the db library, +or maybe the application needs to supply the commit message. + + +# Design + +A filesystem stores two kinds of things: directories and files. +A directory contains files, and other directories. + +Git stores two kinds of things: trees and blobs. Trees contain blobs, +and other trees. + +Maybe this DB tracks two kinds of things: databases and key/value objects. +Databases store key/value objects, and other databases. + +Some things we'd want from this DB layer: + +* Filesystem objects correspond to structs, like how we have each struct + Issue in its own issue directory. + +* Structs are nested, like how struct Issue contains struct Comment + +* Some fields are simple types (`author` is String), some are + less simple (`timestamp` is chrono::DateTime), some are custom + (`state` is enum State), and some are complicated (`dependencies` + is Option>, `comments` is Vec) + +* Filesystem objects are optimized for getting tracked by git - minimize + merge conflicts. From 11989eb68f05aa9d7c9d00b068596e95050f5a31 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 15 Jul 2025 15:09:43 -0600 Subject: [PATCH 288/489] create new issue 63bca383372acb1070f9b2416b2a84f6 --- 63bca383372acb1070f9b2416b2a84f6/description | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 63bca383372acb1070f9b2416b2a84f6/description diff --git a/63bca383372acb1070f9b2416b2a84f6/description b/63bca383372acb1070f9b2416b2a84f6/description new file mode 100644 index 0000000..bb2561d --- /dev/null +++ b/63bca383372acb1070f9b2416b2a84f6/description @@ -0,0 +1,5 @@ +refactor IssuesDatabaseSource / IssuesDatabase / Issues + +If feels like there's a lot of overlap and intimate sharing of +responsibility among these three, and i'm not sure we have the best +abstraction to describe it. From 4413b864143c7edefe55f499b071fe4b35425b9c Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 15 Jul 2025 21:20:49 -0600 Subject: [PATCH 289/489] create new issue efc67db278f79a54a608723f4ce538b6 --- efc67db278f79a54a608723f4ce538b6/description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 efc67db278f79a54a608723f4ce538b6/description diff --git a/efc67db278f79a54a608723f4ce538b6/description b/efc67db278f79a54a608723f4ce538b6/description new file mode 100644 index 0000000..29cb508 --- /dev/null +++ b/efc67db278f79a54a608723f4ce538b6/description @@ -0,0 +1,4 @@ +smart ui scaling + +some thoughts: +make the preview pane hide under issues pane until task is selected when terminal size is too small to display both From a1b410cd50727e912a9200909d9695e7fd5f9775 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 15 Jul 2025 21:23:48 -0600 Subject: [PATCH 290/489] create new issue f3b19da37751e1a77ec5d9580effc713 --- f3b19da37751e1a77ec5d9580effc713/description | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 f3b19da37751e1a77ec5d9580effc713/description diff --git a/f3b19da37751e1a77ec5d9580effc713/description b/f3b19da37751e1a77ec5d9580effc713/description new file mode 100644 index 0000000..2740204 --- /dev/null +++ b/f3b19da37751e1a77ec5d9580effc713/description @@ -0,0 +1,7 @@ +linking + +allows users to follow embedded links to other `ent` content, etc. directly from the TUI + +- allow users to follow comment / issue / etc. +- allow users to follow links +- allow users to open files(?) From 088b6ea4f73e46be25d24648f402748b172d5a12 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 15 Jul 2025 21:24:09 -0600 Subject: [PATCH 291/489] issue f3b19da37751e1a77ec5d9580effc713 add tag tui --- f3b19da37751e1a77ec5d9580effc713/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 f3b19da37751e1a77ec5d9580effc713/tags diff --git a/f3b19da37751e1a77ec5d9580effc713/tags b/f3b19da37751e1a77ec5d9580effc713/tags new file mode 100644 index 0000000..ba0da07 --- /dev/null +++ b/f3b19da37751e1a77ec5d9580effc713/tags @@ -0,0 +1 @@ +tui From 817ff624bc72e4c8a1ee3d2e13b5c05365b08d05 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 15 Jul 2025 21:24:31 -0600 Subject: [PATCH 292/489] issue efc67db278f79a54a608723f4ce538b6 add tag tui --- efc67db278f79a54a608723f4ce538b6/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 efc67db278f79a54a608723f4ce538b6/tags diff --git a/efc67db278f79a54a608723f4ce538b6/tags b/efc67db278f79a54a608723f4ce538b6/tags new file mode 100644 index 0000000..ba0da07 --- /dev/null +++ b/efc67db278f79a54a608723f4ce538b6/tags @@ -0,0 +1 @@ +tui From 5e5508a2ee3a0e380bdd2f4c1897826d439811f1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 15 Jul 2025 16:27:48 -0600 Subject: [PATCH 293/489] Issue: make a helper function to commit an Issue This improves code reuse and streamlines the code a bit. --- src/issue.rs | 73 ++++++++++++++++++++++------------------------------ 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/src/issue.rs b/src/issue.rs index 0de413c..23bb59d 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -243,8 +243,7 @@ impl Issue { None => issue.edit_description_file()?, }; - crate::git::add(&issue_dir)?; - crate::git::commit(&issue_dir, &format!("create new issue {}", issue_id))?; + issue.commit(&format!("create new issue {}", issue_id))?; Ok(issue) } @@ -253,21 +252,15 @@ impl Issue { pub fn edit_description(&mut self) -> Result<(), IssueError> { self.edit_description_file()?; let description_filename = self.description_filename(); - crate::git::add(&description_filename)?; - if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { - crate::git::commit( - &description_filename.parent().unwrap(), - &format!( - "edit description of issue {}", - description_filename - .parent() - .unwrap() - .file_name() - .unwrap() - .to_string_lossy() - ), - )?; - } + self.commit(&format!( + "edit description of issue {}", + description_filename + .parent() + .unwrap() + .file_name() + .unwrap() + .to_string_lossy(), + ))?; Ok(()) } @@ -286,18 +279,12 @@ impl Issue { state_filename.push("state"); let mut state_file = std::fs::File::create(&state_filename)?; write!(state_file, "{}", new_state)?; - crate::git::add(&state_filename)?; - if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { - crate::git::commit( - &self.dir, - &format!( - "change state of issue {}, {} -> {}", - self.dir.file_name().unwrap().to_string_lossy(), - old_state, - new_state, - ), - )?; - } + self.commit(&format!( + "change state of issue {}, {} -> {}", + self.dir.file_name().unwrap().to_string_lossy(), + old_state, + new_state, + ))?; Ok(()) } @@ -319,18 +306,12 @@ impl Issue { assignee_filename.push("assignee"); let mut assignee_file = std::fs::File::create(&assignee_filename)?; write!(assignee_file, "{}", new_assignee)?; - crate::git::add(&assignee_filename)?; - if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { - crate::git::commit( - &self.dir, - &format!( - "change assignee of issue {}, {} -> {}", - self.dir.file_name().unwrap().to_string_lossy(), - old_assignee, - new_assignee, - ), - )?; - } + self.commit(&format!( + "change assignee of issue {}, {} -> {}", + self.dir.file_name().unwrap().to_string_lossy(), + old_assignee, + new_assignee, + ))?; Ok(()) } @@ -444,7 +425,15 @@ impl Issue { for tag in &self.tags { writeln!(tags_file, "{}", tag)?; } - crate::git::add(&tags_filename)?; + self.commit(commit_message)?; + Ok(()) + } + + fn commit(&self, commit_message: &str) -> Result<(), IssueError> { + crate::git::add(&self.dir)?; + if !crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { + return Ok(()); + } crate::git::commit(&self.dir, commit_message)?; Ok(()) } From 20c17f281b656ec135af0526783ee1acd2ce5ce7 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 16 Jul 2025 21:29:42 -0600 Subject: [PATCH 294/489] `ent list FILTER`: the filter now takes multiple strings This is instead of a single big string with chunks separated by ":". ":" is used in RFC 3339 date-time strings (like "2025-07-16 21:23:44 -06:00"), so it's inconvenient to reserve ":" to be the chunk separator. I'm not super wedded to this new Vec way of doing the filter, but it seems fine and convenient for now. --- src/bin/ent/main.rs | 73 +++++++++++++++++++++++++-------------- src/lib.rs | 84 +++++++++++++++++++++++---------------------- 2 files changed, 91 insertions(+), 66 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index b211383..7499a15 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -24,22 +24,24 @@ struct Args { enum Commands { /// List issues. List { - /// Filter string, describes issues to include in the list. - /// The filter string is composed of chunks separated by ":". - /// Each chunk is of the form "name=condition". The supported - /// names and their matching conditions are: + /// Filter strings, describes issues to include in the list. + /// Each filter string is of the form "name=condition". + /// The supported names and their matching conditions are: /// /// "state": Comma-separated list of states to list. + /// Example: "state=new,backlog". Defaults to + /// "new,backlog,blocked,inprogress". /// - /// "assignee": Comma-separated list of assignees to list. - /// Defaults to all assignees if not set. + /// "assignee": Comma-separated list of assignees to include in + /// the list. The empty string includes issues with no assignee. + /// Example: "assignee=seb," lists issues assigned to "seb" and + /// issues without an assignee. Defaults to include all issues. /// - /// "tag": Comma-separated list of tags to include or exclude - /// (if prefixed with "-"). If omitted, defaults to including - /// all tags and excluding none. - /// - #[arg(default_value_t = String::from("state=New,Backlog,Blocked,InProgress"))] - filter: String, + /// "tag": Comma-separated list of tags to include, or exclude + /// if prefixed with "-". Example: "tag=bug,-docs" shows issues + /// that are tagged "bug" and not tagged "docs". Defaults to + /// including all tags and excluding none. + filter: Vec, }, /// Create a new issue. @@ -93,7 +95,14 @@ fn handle_command( match &args.command { Commands::List { filter } => { let issues = entomologist::database::read_issues_database(issues_database_source)?; - let filter = entomologist::Filter::new_from_str(filter)?; + let filter = { + let mut f = entomologist::Filter::new(); + for filter_str in filter { + f.parse(filter_str)?; + } + f + }; + let mut uuids_by_state = std::collections::HashMap::< entomologist::issue::State, Vec<&entomologist::issue::IssueHandle>, @@ -191,8 +200,10 @@ fn handle_command( } Commands::New { description } => { - let issues_database = - entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; + let issues_database = entomologist::database::make_issues_database( + issues_database_source, + entomologist::database::IssuesDatabaseAccess::ReadWrite, + )?; match entomologist::issue::Issue::new(&issues_database.dir, description) { Err(entomologist::issue::IssueError::EmptyDescription) => { println!("no new issue created"); @@ -209,8 +220,10 @@ fn handle_command( } Commands::Edit { uuid } => { - let issues_database = - entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; + 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)?; if let Some(issue) = issues.get_mut_issue(uuid) { match issue.edit_description() { @@ -279,8 +292,10 @@ fn handle_command( new_state, } => match new_state { Some(new_state) => { - let issues_database = - entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; + 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)?; match issues.issues.get_mut(issue_id) { Some(issue) => { @@ -312,8 +327,10 @@ fn handle_command( issue_id, description, } => { - let issues_database = - entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; + 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)); @@ -338,9 +355,13 @@ fn handle_command( } Commands::Sync { remote } => { - if let entomologist::database::IssuesDatabaseSource::Branch(branch) = issues_database_source { - let issues_database = - entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; + if let entomologist::database::IssuesDatabaseSource::Branch(branch) = + issues_database_source + { + let issues_database = entomologist::database::make_issues_database( + issues_database_source, + entomologist::database::IssuesDatabaseAccess::ReadWrite, + )?; entomologist::git::sync(&issues_database.dir, remote, branch)?; println!("synced {:?} with {:?}", branch, remote); } else { @@ -440,7 +461,9 @@ fn main() -> anyhow::Result<()> { // println!("{:?}", args); let issues_database_source = match (&args.issues_dir, &args.issues_branch) { - (Some(dir), None) => entomologist::database::IssuesDatabaseSource::Dir(std::path::Path::new(dir)), + (Some(dir), None) => { + entomologist::database::IssuesDatabaseSource::Dir(std::path::Path::new(dir)) + } (None, Some(branch)) => entomologist::database::IssuesDatabaseSource::Branch(branch), (None, None) => entomologist::database::IssuesDatabaseSource::Branch("entomologist-data"), (Some(_), Some(_)) => { diff --git a/src/lib.rs b/src/lib.rs index 17104ea..dbb19e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,12 @@ use std::str::FromStr; pub mod comment; +pub mod database; pub mod git; pub mod issue; pub mod issues; -pub mod database; + +use crate::issue::State; #[derive(Debug, thiserror::Error)] pub enum ParseFilterError { @@ -26,9 +28,8 @@ pub struct Filter<'a> { } impl<'a> Filter<'a> { - pub fn new_from_str(filter_str: &'a str) -> Result, ParseFilterError> { - use crate::issue::State; - let mut f = Filter { + pub fn new() -> Filter<'a> { + Self { include_states: std::collections::HashSet::::from([ State::InProgress, State::Blocked, @@ -38,51 +39,52 @@ impl<'a> Filter<'a> { include_assignees: std::collections::HashSet::<&'a str>::new(), include_tags: std::collections::HashSet::<&'a str>::new(), exclude_tags: std::collections::HashSet::<&'a str>::new(), - }; + } + } - for filter_chunk_str in filter_str.split(":") { - let tokens: Vec<&str> = filter_chunk_str.split("=").collect(); - if tokens.len() != 2 { - return Err(ParseFilterError::ParseError); + pub fn parse(&mut self, filter_str: &'a str) -> Result<(), ParseFilterError> { + let tokens: Vec<&str> = filter_str.split("=").collect(); + if tokens.len() != 2 { + return Err(ParseFilterError::ParseError); + } + + match tokens[0] { + "state" => { + self.include_states.clear(); + for s in tokens[1].split(",") { + self.include_states + .insert(crate::issue::State::from_str(s)?); + } } - match tokens[0] { - "state" => { - f.include_states.clear(); - for s in tokens[1].split(",") { - f.include_states.insert(crate::issue::State::from_str(s)?); + "assignee" => { + self.include_assignees.clear(); + for s in tokens[1].split(",") { + self.include_assignees.insert(s); + } + } + + "tag" => { + self.include_tags.clear(); + self.exclude_tags.clear(); + for s in tokens[1].split(",") { + if s.len() == 0 { + return Err(ParseFilterError::ParseError); + } + if s.chars().nth(0).unwrap() == '-' { + self.exclude_tags.insert(&s[1..]); + } else { + self.include_tags.insert(s); } } + } - "assignee" => { - f.include_assignees.clear(); - for s in tokens[1].split(",") { - f.include_assignees.insert(s); - } - } - - "tag" => { - f.include_tags.clear(); - f.exclude_tags.clear(); - for s in tokens[1].split(",") { - if s.len() == 0 { - return Err(ParseFilterError::ParseError); - } - if s.chars().nth(0).unwrap() == '-' { - f.exclude_tags.insert(&s[1..]); - } else { - f.include_tags.insert(s); - } - } - } - - _ => { - println!("unknown filter chunk '{}'", filter_chunk_str); - return Err(ParseFilterError::ParseError); - } + _ => { + println!("unknown filter string '{}'", filter_str); + return Err(ParseFilterError::ParseError); } } - Ok(f) + Ok(()) } } From 3df76b89df28c6eb8081732a314eaa7b5b9a8dfa Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 15 Jul 2025 15:10:34 -0600 Subject: [PATCH 295/489] rename Issue and Comment `timestamp` to `creation_time` This is to make room for a second timestamp that records when the issue was marked Done. --- src/bin/ent/main.rs | 6 +++--- src/comment.rs | 10 +++++----- src/issue.rs | 14 +++++++------- src/issues.rs | 16 ++++++++-------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 7499a15..494b1ba 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -155,7 +155,7 @@ fn handle_command( these_uuids.sort_by(|a_id, b_id| { let a = issues.issues.get(*a_id).unwrap(); let b = issues.issues.get(*b_id).unwrap(); - a.timestamp.cmp(&b.timestamp) + a.creation_time.cmp(&b.creation_time) }); println!("{:?}:", state); for uuid in these_uuids { @@ -262,7 +262,7 @@ fn handle_command( Some(issue) => { println!("issue {}", issue_id); println!("author: {}", issue.author); - println!("timestamp: {}", issue.timestamp); + println!("creation_time: {}", issue.creation_time); println!("state: {:?}", issue.state); if let Some(dependencies) = &issue.dependencies { println!("dependencies: {:?}", dependencies); @@ -276,7 +276,7 @@ fn handle_command( println!(""); println!("comment: {}", comment.uuid); println!("author: {}", comment.author); - println!("timestamp: {}", comment.timestamp); + println!("creation_time: {}", comment.creation_time); println!(""); println!("{}", comment.description); } diff --git a/src/comment.rs b/src/comment.rs index c8e26c9..216b34f 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -4,7 +4,7 @@ use std::io::{IsTerminal, Write}; pub struct Comment { pub uuid: String, pub author: String, - pub timestamp: chrono::DateTime, + pub creation_time: chrono::DateTime, pub description: String, /// This is the directory that the comment lives in. Only used @@ -53,13 +53,13 @@ impl Comment { } let author = crate::git::git_log_oldest_author(comment_dir)?; - let timestamp = crate::git::git_log_oldest_timestamp(comment_dir)?; + let creation_time = crate::git::git_log_oldest_timestamp(comment_dir)?; let dir = std::path::PathBuf::from(comment_dir); Ok(Self { uuid: String::from(dir.file_name().unwrap().to_string_lossy()), author, - timestamp, + creation_time, description: description.unwrap(), dir: std::path::PathBuf::from(comment_dir), }) @@ -84,7 +84,7 @@ impl Comment { let mut comment = crate::comment::Comment { uuid, author: String::from(""), // this will be updated from git when we re-read this comment - timestamp: chrono::Local::now(), + creation_time: chrono::Local::now(), description: String::from(""), // this will be set immediately below dir: dir.clone(), }; @@ -204,7 +204,7 @@ mod tests { let expected = Comment { uuid: String::from("9055dac36045fe36545bed7ae7b49347"), author: String::from("Sebastian Kuzminsky "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00") .unwrap() .with_timezone(&chrono::Local), description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561\n\nIt has multiple lines\n"), diff --git a/src/issue.rs b/src/issue.rs index 23bb59d..72c1487 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -22,7 +22,7 @@ pub type IssueHandle = String; pub struct Issue { pub id: String, pub author: String, - pub timestamp: chrono::DateTime, + pub creation_time: chrono::DateTime, pub tags: Vec, pub state: State, pub dependencies: Option>, @@ -159,12 +159,12 @@ impl Issue { }; let author = crate::git::git_log_oldest_author(dir)?; - let timestamp = crate::git::git_log_oldest_timestamp(dir)?; + let creation_time = crate::git::git_log_oldest_timestamp(dir)?; Ok(Self { id, author, - timestamp, + creation_time, tags, state: state, dependencies, @@ -185,7 +185,7 @@ impl Issue { comments.push(comment); } } - comments.sort_by(|a, b| a.timestamp.cmp(&b.timestamp)); + comments.sort_by(|a, b| a.creation_time.cmp(&b.creation_time)); Ok(()) } @@ -220,7 +220,7 @@ impl Issue { let mut issue = Self { id: String::from(&issue_id), author: String::from(""), - timestamp: chrono::Local::now(), + creation_time: chrono::Local::now(), tags: Vec::::new(), state: State::New, dependencies: None, @@ -450,7 +450,7 @@ mod tests { let expected = Issue { id: String::from("3943fc5c173fdf41c0a22251593cd476d96e6c9f"), author: String::from("Sebastian Kuzminsky "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), tags: Vec::::from([ @@ -477,7 +477,7 @@ mod tests { let expected = Issue { id: String::from("7792b063eef6d33e7da5dc1856750c149ba678c6"), author: String::from("Sebastian Kuzminsky "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), tags: Vec::::new(), diff --git a/src/issues.rs b/src/issues.rs index 42e964d..7f2e63e 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -90,7 +90,7 @@ mod tests { expected.add_issue(crate::issue::Issue { id: uuid, author: String::from("Sebastian Kuzminsky "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), tags: Vec::::new(), @@ -109,7 +109,7 @@ mod tests { crate::issue::Issue { id: uuid, author: String::from("Sebastian Kuzminsky "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), tags: Vec::::from([ @@ -141,7 +141,7 @@ mod tests { expected.add_issue(crate::issue::Issue { id: uuid, author: String::from("Sebastian Kuzminsky "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") .unwrap() .with_timezone(&chrono::Local), tags: Vec::::new(), @@ -165,7 +165,7 @@ mod tests { crate::comment::Comment { uuid: comment_uuid, author: String::from("Sebastian Kuzminsky "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00").unwrap().with_timezone(&chrono::Local), + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00").unwrap().with_timezone(&chrono::Local), description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561\n\nIt has multiple lines\n"), dir: std::path::PathBuf::from(comment_dir), } @@ -174,7 +174,7 @@ mod tests { crate::issue::Issue { id: uuid, author: String::from("Sebastian Kuzminsky "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") .unwrap() .with_timezone(&chrono::Local), tags: Vec::::new(), @@ -202,7 +202,7 @@ mod tests { expected.add_issue(crate::issue::Issue { id: uuid, author: String::from("sigil-03 "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() .with_timezone(&chrono::Local), tags: Vec::::new(), @@ -221,7 +221,7 @@ mod tests { crate::issue::Issue { id: uuid, author: String::from("sigil-03 "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() .with_timezone(&chrono::Local), tags: Vec::::new(), @@ -241,7 +241,7 @@ mod tests { crate::issue::Issue { id: uuid, author: String::from("sigil-03 "), - timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() .with_timezone(&chrono::Local), tags: Vec::::new(), From 3b33ed41f5463309a4f9001fae9f6d278e61aabe Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 15 Jul 2025 15:37:23 -0600 Subject: [PATCH 296/489] Issue: add `done_time` field This records the DateTime that the issue moved to the Done state (if any). --- src/issue.rs | 13 +++++++++++++ src/issues.rs | 11 +++++++++++ .../done_time | 1 + 3 files changed, 25 insertions(+) create mode 100644 test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/done_time diff --git a/src/issue.rs b/src/issue.rs index 72c1487..927eb7f 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -23,6 +23,7 @@ pub struct Issue { pub id: String, pub author: String, pub creation_time: chrono::DateTime, + pub done_time: Option>, pub tags: Vec, pub state: State, pub dependencies: Option>, @@ -43,6 +44,8 @@ pub enum IssueError { EnvVarError(#[from] std::env::VarError), #[error(transparent)] CommentError(#[from] crate::comment::CommentError), + #[error(transparent)] + ChronoParseError(#[from] chrono::format::ParseError), #[error("Failed to parse issue")] IssueParseError, #[error("Failed to parse state")] @@ -106,6 +109,7 @@ impl Issue { let mut comments = Vec::::new(); let mut assignee: Option = None; let mut tags = Vec::::new(); + let mut done_time: Option> = None; for direntry in dir.read_dir()? { if let Ok(direntry) = direntry { @@ -119,6 +123,11 @@ impl Issue { assignee = Some(String::from( std::fs::read_to_string(direntry.path())?.trim(), )); + } else if file_name == "done_time" { + let raw_done_time = chrono::DateTime::<_>::parse_from_rfc3339( + std::fs::read_to_string(direntry.path())?.trim(), + )?; + done_time = Some(raw_done_time.into()); } else if file_name == "dependencies" { let dep_strings = std::fs::read_to_string(direntry.path())?; let deps: Vec = dep_strings @@ -165,6 +174,7 @@ impl Issue { id, author, creation_time, + done_time, tags, state: state, dependencies, @@ -221,6 +231,7 @@ impl Issue { id: String::from(&issue_id), author: String::from(""), creation_time: chrono::Local::now(), + done_time: None, tags: Vec::::new(), state: State::New, dependencies: None, @@ -453,6 +464,7 @@ mod tests { creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), + done_time: None, tags: Vec::::from([ String::from("tag1"), String::from("TAG2"), @@ -480,6 +492,7 @@ mod tests { creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), + done_time: None, tags: Vec::::new(), state: State::InProgress, dependencies: None, diff --git a/src/issues.rs b/src/issues.rs index 7f2e63e..709a79d 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -93,6 +93,7 @@ mod tests { creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), + done_time: None, tags: Vec::::new(), state: crate::issue::State::InProgress, dependencies: None, @@ -112,6 +113,7 @@ mod tests { creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), + done_time: None, tags: Vec::::from([ String::from("tag1"), String::from("TAG2"), @@ -144,6 +146,11 @@ mod tests { creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") .unwrap() .with_timezone(&chrono::Local), + done_time: Some( + chrono::DateTime::parse_from_rfc3339("2025-07-15T15:15:15-06:00") + .unwrap() + .with_timezone(&chrono::Local), + ), tags: Vec::::new(), state: crate::issue::State::Done, dependencies: None, @@ -177,6 +184,7 @@ mod tests { creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") .unwrap() .with_timezone(&chrono::Local), + done_time: None, tags: Vec::::new(), state: crate::issue::State::WontDo, dependencies: None, @@ -205,6 +213,7 @@ mod tests { creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() .with_timezone(&chrono::Local), + done_time: None, tags: Vec::::new(), state: crate::issue::State::Done, dependencies: None, @@ -224,6 +233,7 @@ mod tests { creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() .with_timezone(&chrono::Local), + done_time: None, tags: Vec::::new(), state: crate::issue::State::WontDo, dependencies: None, @@ -244,6 +254,7 @@ mod tests { creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() .with_timezone(&chrono::Local), + done_time: None, tags: Vec::::new(), state: crate::issue::State::WontDo, dependencies: Some(vec![ diff --git a/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/done_time b/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/done_time new file mode 100644 index 0000000..d455c4d --- /dev/null +++ b/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/done_time @@ -0,0 +1 @@ +2025-07-15T15:15:15-06:00 From bc2b1bd3c15aa6c8a511904bb1effcdcafc4f961 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 15 Jul 2025 16:28:14 -0600 Subject: [PATCH 297/489] add API and CLI to get & set done-time of an issue --- src/bin/ent/main.rs | 41 +++++++++++++++++++++++++++++++++++++++++ src/issue.rs | 24 +++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 494b1ba..ac55045 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -86,6 +86,12 @@ enum Commands { #[arg(allow_hyphen_values = true)] tag: Option, }, + + /// Get or set the `done_time` of the Issue. + DoneTime { + issue_id: String, + done_time: Option, + }, } fn handle_command( @@ -263,6 +269,9 @@ fn handle_command( println!("issue {}", issue_id); println!("author: {}", issue.author); println!("creation_time: {}", issue.creation_time); + if let Some(done_time) = &issue.done_time { + println!("done_time: {}", done_time); + } println!("state: {:?}", issue.state); if let Some(dependencies) = &issue.dependencies { println!("dependencies: {:?}", dependencies); @@ -448,6 +457,38 @@ 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 = chrono::DateTime::parse_from_rfc3339(done_time) + .unwrap() + .with_timezone(&chrono::Local); + issue.set_done_time(done_time)?; + } + None => match &issue.done_time { + Some(done_time) => println!("done_time: {}", done_time), + None => println!("None"), + }, + }; + } } Ok(()) diff --git a/src/issue.rs b/src/issue.rs index 927eb7f..bc3d959 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -283,7 +283,8 @@ impl Issue { } } - /// Change the State of the Issue. + /// Change the State of the Issue. If the new state is `Done`, + /// set the Issue `done_time`. Commits. pub fn set_state(&mut self, new_state: State) -> Result<(), IssueError> { let old_state = self.state.clone(); let mut state_filename = std::path::PathBuf::from(&self.dir); @@ -296,6 +297,9 @@ impl Issue { old_state, new_state, ))?; + if new_state == State::Done { + self.set_done_time(chrono::Local::now())?; + } Ok(()) } @@ -307,6 +311,24 @@ impl Issue { Ok(()) } + /// Set the `done_time` of the Issue. Commits. + pub fn set_done_time( + &mut self, + done_time: chrono::DateTime, + ) -> Result<(), IssueError> { + let mut done_time_filename = std::path::PathBuf::from(&self.dir); + done_time_filename.push("done_time"); + let mut done_time_file = std::fs::File::create(&done_time_filename)?; + write!(done_time_file, "{}", done_time.to_rfc3339())?; + self.done_time = Some(done_time.clone()); + self.commit(&format!( + "set done-time of issue {} to {}", + self.dir.file_name().unwrap().to_string_lossy(), + done_time, + ))?; + Ok(()) + } + /// Set the Assignee of an Issue. pub fn set_assignee(&mut self, new_assignee: &str) -> Result<(), IssueError> { let old_assignee = match &self.assignee { From a3077ca31321757772a6d6d1f283dca0c4cdb40e Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 16 Jul 2025 21:28:15 -0600 Subject: [PATCH 298/489] `ent list FILTER`: add filter "done-time=[START]..[END]" --- src/bin/ent/main.rs | 20 ++++++++++++++++++++ src/lib.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index ac55045..a597fd9 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -41,6 +41,13 @@ enum Commands { /// if prefixed with "-". Example: "tag=bug,-docs" shows issues /// that are tagged "bug" and not tagged "docs". Defaults to /// including all tags and excluding none. + /// + /// "done-time": Time range of issue completion, in the form + /// "[START]..[END]". Includes issues that were marked Done + /// between START and END. START and END are both in RFC 3339 + /// format, e.g. "YYYY-MM-DDTHH:MM:SS[+-]HH:MM". If START + /// is omitted, defaults to the beginning of time. If END is + /// omitted, defaults to the end of time. filter: Vec, }, @@ -138,6 +145,19 @@ fn handle_command( } } + if let Some(issue_done_time) = issue.done_time { + if let Some(start_done_time) = filter.start_done_time { + if start_done_time > issue_done_time { + continue; + } + } + if let Some(end_done_time) = filter.end_done_time { + if end_done_time < issue_done_time { + continue; + } + } + } + // This issue passed all the filters, include it in list. uuids_by_state .entry(issue.state.clone()) diff --git a/src/lib.rs b/src/lib.rs index dbb19e5..b6245b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ pub enum ParseFilterError { ParseError, #[error(transparent)] IssueParseError(#[from] crate::issue::IssueError), + #[error(transparent)] + ChronoParseError(#[from] chrono::format::ParseError), } // FIXME: It's easy to imagine a full dsl for filtering issues, for now @@ -25,6 +27,8 @@ pub struct Filter<'a> { pub include_assignees: std::collections::HashSet<&'a str>, pub include_tags: std::collections::HashSet<&'a str>, pub exclude_tags: std::collections::HashSet<&'a str>, + pub start_done_time: Option>, + pub end_done_time: Option>, } impl<'a> Filter<'a> { @@ -39,6 +43,8 @@ impl<'a> Filter<'a> { include_assignees: std::collections::HashSet::<&'a str>::new(), include_tags: std::collections::HashSet::<&'a str>::new(), exclude_tags: std::collections::HashSet::<&'a str>::new(), + start_done_time: None, + end_done_time: None, } } @@ -79,6 +85,27 @@ impl<'a> Filter<'a> { } } + "done-time" => { + self.start_done_time = None; + self.end_done_time = None; + let times: Vec<&str> = tokens[1].split("..").collect(); + if times.len() > 2 { + return Err(ParseFilterError::ParseError); + } + if times[0].len() != 0 { + self.start_done_time = Some( + chrono::DateTime::parse_from_rfc3339(times[0])? + .with_timezone(&chrono::Local), + ); + } + if times[1].len() != 0 { + self.end_done_time = Some( + chrono::DateTime::parse_from_rfc3339(times[1])? + .with_timezone(&chrono::Local), + ); + } + } + _ => { println!("unknown filter string '{}'", filter_str); return Err(ParseFilterError::ParseError); From 3e0ab7092e5afa4367c5349eb76a118b9ae3962e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 15 Jul 2025 10:56:09 -0600 Subject: [PATCH 299/489] update CLI to print the issue ID when a new issue is created --- src/bin/ent/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index b211383..fff83e1 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -203,6 +203,7 @@ fn handle_command( } Ok(issue) => { println!("created new issue '{}'", issue.title()); + println!("ID: {}", issue.id); return Ok(()); } } From a363acad0d0c727cfbfcff9e0f19f2809cb1367a Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 12:05:04 -0600 Subject: [PATCH 300/489] create new issue 5f59a02e5320b62a68ad704da17e0ff4 --- 5f59a02e5320b62a68ad704da17e0ff4/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 5f59a02e5320b62a68ad704da17e0ff4/description diff --git a/5f59a02e5320b62a68ad704da17e0ff4/description b/5f59a02e5320b62a68ad704da17e0ff4/description new file mode 100644 index 0000000..3956415 --- /dev/null +++ b/5f59a02e5320b62a68ad704da17e0ff4/description @@ -0,0 +1,3 @@ +add `ent update` + +allow `ent` to update itself when commanded to From b12e3cc248d550d667eb2c14d5f19d39b6ed2010 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 13:24:00 -0600 Subject: [PATCH 301/489] create new issue 54e366c80dfc6fc2dd5d52eb36023386 --- 54e366c80dfc6fc2dd5d52eb36023386/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 54e366c80dfc6fc2dd5d52eb36023386/description diff --git a/54e366c80dfc6fc2dd5d52eb36023386/description b/54e366c80dfc6fc2dd5d52eb36023386/description new file mode 100644 index 0000000..9a55450 --- /dev/null +++ b/54e366c80dfc6fc2dd5d52eb36023386/description @@ -0,0 +1 @@ +filter issues with comment activity in a particular timeframe From 75f99040e50b1ba6581e7e664249e892e74bfa84 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 18 Jul 2025 10:24:05 -0600 Subject: [PATCH 302/489] add comment ef62c5e84ed7e3571f9d9c8ced81f49d on issue 5f59a02e5320b62a68ad704da17e0ff4 --- .../comments/ef62c5e84ed7e3571f9d9c8ced81f49d/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 5f59a02e5320b62a68ad704da17e0ff4/comments/ef62c5e84ed7e3571f9d9c8ced81f49d/description diff --git a/5f59a02e5320b62a68ad704da17e0ff4/comments/ef62c5e84ed7e3571f9d9c8ced81f49d/description b/5f59a02e5320b62a68ad704da17e0ff4/comments/ef62c5e84ed7e3571f9d9c8ced81f49d/description new file mode 100644 index 0000000..42a7bac --- /dev/null +++ b/5f59a02e5320b62a68ad704da17e0ff4/comments/ef62c5e84ed7e3571f9d9c8ced81f49d/description @@ -0,0 +1,3 @@ +What do you mean by ent updating itself? + +Do you mean it git clones itself and cargo builds and installs itself? From b7144bb5ff79a76ab443e154dd5903eb7f7a6044 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 18 Jul 2025 10:30:09 -0600 Subject: [PATCH 303/489] create new issue 781360ade670846ed0ccdbfd19ffa8fd --- 781360ade670846ed0ccdbfd19ffa8fd/description | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 781360ade670846ed0ccdbfd19ffa8fd/description diff --git a/781360ade670846ed0ccdbfd19ffa8fd/description b/781360ade670846ed0ccdbfd19ffa8fd/description new file mode 100644 index 0000000..d74158f --- /dev/null +++ b/781360ade670846ed0ccdbfd19ffa8fd/description @@ -0,0 +1,15 @@ +add a layer to the architecture with a DB api between the FS and ent? + +This issue grew out of the discussion in issue +08f0d7ee7842c439382816d21ec1dea2. + +Currently the entomologist crate code directly reads and writes files +and calls `git commit` when it wants. The directory is managed by the +application, generally as an ephemeral worktree containing a (possibly +detached) checkout of the `entomologist-data` branch. + +We may be able to simplify the ent internals (and the application) +by adding a "database" API between ent and the filesystem. + +There is some preliminary design brainstorming in the issue mentioned +above. From 5cdf073fc198d2d979f6ad98fc6b05d37e4fd231 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 18 Jul 2025 10:33:19 -0600 Subject: [PATCH 304/489] set done-time of issue 8c73c9fd5bc4f551ee5069035ae6e866 to 2025-07-07 22:29:25 -06:00 --- 8c73c9fd5bc4f551ee5069035ae6e866/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 8c73c9fd5bc4f551ee5069035ae6e866/done_time diff --git a/8c73c9fd5bc4f551ee5069035ae6e866/done_time b/8c73c9fd5bc4f551ee5069035ae6e866/done_time new file mode 100644 index 0000000..f730d86 --- /dev/null +++ b/8c73c9fd5bc4f551ee5069035ae6e866/done_time @@ -0,0 +1 @@ +2025-07-07T22:29:25-06:00 \ No newline at end of file From 07bdf39c23304503c11c9615b224978a80e0620c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 18 Jul 2025 10:34:12 -0600 Subject: [PATCH 305/489] create new issue 7bf773d64437d6b92b7ffe6932531533 --- 7bf773d64437d6b92b7ffe6932531533/description | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 7bf773d64437d6b92b7ffe6932531533/description diff --git a/7bf773d64437d6b92b7ffe6932531533/description b/7bf773d64437d6b92b7ffe6932531533/description new file mode 100644 index 0000000..9defc62 --- /dev/null +++ b/7bf773d64437d6b92b7ffe6932531533/description @@ -0,0 +1,9 @@ +fix parse error in `ent done-time` + +Got this error when i typoed the RFC 3339 time (missing `:` in timezone +offset): + $ ent done-time 8c73c9fd5bc4f551ee5069035ae6e866 2025-07-07T22:29:25-0600 + + thread 'main' panicked at src/bin/ent/main.rs:503:26: + called `Result::unwrap()` on an `Err` value: ParseError(Invalid) + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace From f93802fd90028b92d40d22baba08c2b86560c09a Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 18 Jul 2025 12:34:12 -0600 Subject: [PATCH 306/489] set done-time of issue 75cefad80aacbf23fc7b9c24a75aa236 to 2025-07-09 11:03:45 -06:00 --- 75cefad80aacbf23fc7b9c24a75aa236/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 75cefad80aacbf23fc7b9c24a75aa236/done_time diff --git a/75cefad80aacbf23fc7b9c24a75aa236/done_time b/75cefad80aacbf23fc7b9c24a75aa236/done_time new file mode 100644 index 0000000..63ac4c4 --- /dev/null +++ b/75cefad80aacbf23fc7b9c24a75aa236/done_time @@ -0,0 +1 @@ +2025-07-09T11:03:45-06:00 \ No newline at end of file From 9f91dd97287f9f182dd7951c7fd10eca45146c1b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 18 Jul 2025 12:34:58 -0600 Subject: [PATCH 307/489] set done-time of issue 7da3bd5b72de0a05936b094db5d24304 to 2025-07-11 20:32:23 -06:00 --- 7da3bd5b72de0a05936b094db5d24304/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 7da3bd5b72de0a05936b094db5d24304/done_time diff --git a/7da3bd5b72de0a05936b094db5d24304/done_time b/7da3bd5b72de0a05936b094db5d24304/done_time new file mode 100644 index 0000000..946bd9b --- /dev/null +++ b/7da3bd5b72de0a05936b094db5d24304/done_time @@ -0,0 +1 @@ +2025-07-11T20:32:23-06:00 \ No newline at end of file From f9f20d448aad148b902b9bf45698d4cc4eef4e2b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 18 Jul 2025 12:36:23 -0600 Subject: [PATCH 308/489] set done-time of issue 198a7d56a19f0579fbc04f2ee9cc234f to 2025-07-07 16:49:13 -06:00 --- 198a7d56a19f0579fbc04f2ee9cc234f/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 198a7d56a19f0579fbc04f2ee9cc234f/done_time diff --git a/198a7d56a19f0579fbc04f2ee9cc234f/done_time b/198a7d56a19f0579fbc04f2ee9cc234f/done_time new file mode 100644 index 0000000..8d26bab --- /dev/null +++ b/198a7d56a19f0579fbc04f2ee9cc234f/done_time @@ -0,0 +1 @@ +2025-07-07T16:49:13-06:00 \ No newline at end of file From 695963a03850ff8d0aa325ff596d45ed663de057 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 18 Jul 2025 12:40:40 -0600 Subject: [PATCH 309/489] create new issue c1f6bcd5727bf3e0e5a4f4b79d8242ab --- c1f6bcd5727bf3e0e5a4f4b79d8242ab/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 c1f6bcd5727bf3e0e5a4f4b79d8242ab/description diff --git a/c1f6bcd5727bf3e0e5a4f4b79d8242ab/description b/c1f6bcd5727bf3e0e5a4f4b79d8242ab/description new file mode 100644 index 0000000..c93f050 --- /dev/null +++ b/c1f6bcd5727bf3e0e5a4f4b79d8242ab/description @@ -0,0 +1 @@ +teach `ent list FILTER` a new filter type that searches issue & comment descriptions From 7ec495334bb92a2caa1ae0bdf85ff664ffc45567 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 14:57:51 -0600 Subject: [PATCH 310/489] add comment 939decab121b6b4b3095a3b20d5d13f4 on issue 7e2a3a59fb6b77403ff1035255367607 --- .../comments/939decab121b6b4b3095a3b20d5d13f4/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 7e2a3a59fb6b77403ff1035255367607/comments/939decab121b6b4b3095a3b20d5d13f4/description diff --git a/7e2a3a59fb6b77403ff1035255367607/comments/939decab121b6b4b3095a3b20d5d13f4/description b/7e2a3a59fb6b77403ff1035255367607/comments/939decab121b6b4b3095a3b20d5d13f4/description new file mode 100644 index 0000000..38f265e --- /dev/null +++ b/7e2a3a59fb6b77403ff1035255367607/comments/939decab121b6b4b3095a3b20d5d13f4/description @@ -0,0 +1,3 @@ +for now, let's just add the ability to mark dependencies, and then later we can build functionality on top of it which allows an application to decide what to do based on the state of those dependencies. + +i think regardless we should always keep the dependencies, unless they get removed intentionally, and should instead operate off of the `state` of those dependencies. From 1abd4fd0f4206bc61168f3ac5ec105f373ae8504 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 15:54:03 -0600 Subject: [PATCH 311/489] create new issue 1c771196c49c732932caacfa79ad56dc --- 1c771196c49c732932caacfa79ad56dc/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 1c771196c49c732932caacfa79ad56dc/description diff --git a/1c771196c49c732932caacfa79ad56dc/description b/1c771196c49c732932caacfa79ad56dc/description new file mode 100644 index 0000000..2b392da --- /dev/null +++ b/1c771196c49c732932caacfa79ad56dc/description @@ -0,0 +1 @@ +add get() and get_mut() API to database From 91e74a67e60089bf7c5755a501381cb9a03d2e8e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 15:54:11 -0600 Subject: [PATCH 312/489] issue 1c771196c49c732932caacfa79ad56dc add tag db --- 1c771196c49c732932caacfa79ad56dc/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 1c771196c49c732932caacfa79ad56dc/tags diff --git a/1c771196c49c732932caacfa79ad56dc/tags b/1c771196c49c732932caacfa79ad56dc/tags new file mode 100644 index 0000000..65eef93 --- /dev/null +++ b/1c771196c49c732932caacfa79ad56dc/tags @@ -0,0 +1 @@ +db From 6e131d3889a3462a8aa8a224b76cf7cd80a4560f Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 18 Jul 2025 16:02:08 -0600 Subject: [PATCH 313/489] add comment b4d084918bb227469cd80a9a1b190833 on issue c1f6bcd5727bf3e0e5a4f4b79d8242ab --- .../comments/b4d084918bb227469cd80a9a1b190833/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 c1f6bcd5727bf3e0e5a4f4b79d8242ab/comments/b4d084918bb227469cd80a9a1b190833/description diff --git a/c1f6bcd5727bf3e0e5a4f4b79d8242ab/comments/b4d084918bb227469cd80a9a1b190833/description b/c1f6bcd5727bf3e0e5a4f4b79d8242ab/comments/b4d084918bb227469cd80a9a1b190833/description new file mode 100644 index 0000000..cc96c5e --- /dev/null +++ b/c1f6bcd5727bf3e0e5a4f4b79d8242ab/comments/b4d084918bb227469cd80a9a1b190833/description @@ -0,0 +1 @@ +This issue duplicates 0f8b0c982bcfe7d5406bea58301014bc From 79a983d430fac816646e0534227f742d6f8f4b4c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Fri, 18 Jul 2025 16:02:31 -0600 Subject: [PATCH 314/489] change state of issue c1f6bcd5727bf3e0e5a4f4b79d8242ab, new -> wontdo --- c1f6bcd5727bf3e0e5a4f4b79d8242ab/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 c1f6bcd5727bf3e0e5a4f4b79d8242ab/state diff --git a/c1f6bcd5727bf3e0e5a4f4b79d8242ab/state b/c1f6bcd5727bf3e0e5a4f4b79d8242ab/state new file mode 100644 index 0000000..9e3b09d --- /dev/null +++ b/c1f6bcd5727bf3e0e5a4f4b79d8242ab/state @@ -0,0 +1 @@ +wontdo \ No newline at end of file From 8319a4f118d5735c33e377b71d7cb689d2cdffb8 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:20:17 -0600 Subject: [PATCH 315/489] add dependency API / fix dependency representation / dependency management via CLI --- src/bin/ent/main.rs | 45 ++++++++++++++++++++++++++- src/issue.rs | 74 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 109 insertions(+), 10 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 1698954..4118ed3 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -99,6 +99,12 @@ enum Commands { issue_id: String, done_time: Option, }, + + /// get or add a dependency to the issue + Depend { + issue_id: String, + dependency_id: Option, + }, } fn handle_command( @@ -510,6 +516,43 @@ fn handle_command( }, }; } + + Commands::Depend { + issue_id, + dependency_id, + } => match dependency_id { + Some(dep_id) => { + let ent_db = entomologist::database::make_issues_database( + issues_database_source, + entomologist::database::IssuesDatabaseAccess::ReadWrite, + )?; + let mut issues = entomologist::issues::Issues::new_from_dir(&ent_db.dir)?; + if issues.issues.contains_key(dep_id) { + if let Some(issue) = issues.issues.get_mut(issue_id) { + issue.add_dependency(dep_id.clone())?; + } else { + Err(anyhow::anyhow!("issue {} not found", issue_id))?; + }; + } else { + Err(anyhow::anyhow!("dependency {} not found", dep_id))?; + }; + } + None => { + let ent_db = entomologist::database::read_issues_database(issues_database_source)?; + + let Some(issue) = ent_db.issues.get(issue_id) else { + Err(anyhow::anyhow!("issue {} not found", issue_id))? + }; + println!("DEPENDENCIES:"); + if let Some(list) = &issue.dependencies { + for dependency in list { + println!("{}", dependency); + } + } else { + println!("NONE"); + } + } + }, } Ok(()) @@ -531,7 +574,7 @@ fn main() -> anyhow::Result<()> { (Some(_), Some(_)) => { return Err(anyhow::anyhow!( "don't specify both `--issues-dir` and `--issues-branch`" - )) + )); } }; diff --git a/src/issue.rs b/src/issue.rs index bc3d959..363e2b7 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -62,6 +62,12 @@ pub enum IssueError { StdioIsNotTerminal, #[error("Failed to parse issue ID")] IdError, + #[error("Dependency not found")] + DepNotFound, + #[error("Dependency already exists")] + DepExists, + #[error("Self-dependency not allowed")] + DepSelf, } impl FromStr for State { @@ -128,15 +134,8 @@ impl Issue { std::fs::read_to_string(direntry.path())?.trim(), )?; done_time = Some(raw_done_time.into()); - } else if file_name == "dependencies" { - let dep_strings = std::fs::read_to_string(direntry.path())?; - let deps: Vec = dep_strings - .lines() - .map(|dep| IssueHandle::from(dep)) - .collect(); - if deps.len() > 0 { - dependencies = Some(deps); - } + } else if file_name == "dependencies" && direntry.metadata()?.is_dir() { + dependencies = Self::read_dependencies(&direntry.path())?; } else if file_name == "tags" { let contents = std::fs::read_to_string(direntry.path())?; tags = contents @@ -199,6 +198,23 @@ impl Issue { Ok(()) } + fn read_dependencies(dir: &std::path::Path) -> Result>, IssueError> { + let mut dependencies: Option> = None; + for direntry in dir.read_dir()? { + if let Ok(direntry) = direntry { + match &mut dependencies { + Some(deps) => { + deps.push(direntry.file_name().into_string().unwrap()); + } + None => { + dependencies = Some(vec![direntry.file_name().into_string().unwrap()]); + } + } + } + } + Ok(dependencies) + } + /// Add a new Comment to the Issue. Commits. pub fn add_comment( &mut self, @@ -392,6 +408,46 @@ impl Issue { } return false; } + + pub fn add_dependency(&mut self, dep: IssueHandle) -> Result<(), IssueError> { + if self.id == dep { + Err(IssueError::DepSelf)?; + } + match &mut self.dependencies { + Some(v) => v.push(dep.clone()), + None => self.dependencies = Some(vec![dep.clone()]), + } + let mut dir = std::path::PathBuf::from(&self.dir); + dir.push("dependencies"); + if !dir.exists() { + std::fs::create_dir(&dir)?; + } + + dir.push(dep.clone()); + + if !dir.exists() { + std::fs::File::create(&dir)?; + self.commit(&format!("add dep {} to issue {}", dep, self.id))?; + } else { + Err(IssueError::DepExists)?; + } + Ok(()) + } + + pub fn remove_dependency(&mut self, dep: IssueHandle) -> Result<(), IssueError> { + match &mut self.dependencies { + Some(v) => { + if let Some(i) = v.iter().position(|d| d == &dep) { + v.remove(i); + } else { + Err(IssueError::DepNotFound)?; + } + } + None => Err(IssueError::DepNotFound)?, + } + self.commit(&format!("remove dep {} from issue {}", dep, self.id))?; + Ok(()) + } } // This is the internal/private API of Issue. From d6550b9142bd13435e456e0d7248f04bc884875f Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:22:31 -0600 Subject: [PATCH 316/489] change state of issue f14bd410300d6d8802d873c6b584c4aa, inprogress -> done --- f14bd410300d6d8802d873c6b584c4aa/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/f14bd410300d6d8802d873c6b584c4aa/state b/f14bd410300d6d8802d873c6b584c4aa/state index 505c028..348ebd9 100644 --- a/f14bd410300d6d8802d873c6b584c4aa/state +++ b/f14bd410300d6d8802d873c6b584c4aa/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 3dc9d3309bc65e7fdc56a37ef7227d98b241120c Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:22:31 -0600 Subject: [PATCH 317/489] set done-time of issue f14bd410300d6d8802d873c6b584c4aa to 2025-07-18 16:22:31.222641244 -06:00 --- f14bd410300d6d8802d873c6b584c4aa/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 f14bd410300d6d8802d873c6b584c4aa/done_time diff --git a/f14bd410300d6d8802d873c6b584c4aa/done_time b/f14bd410300d6d8802d873c6b584c4aa/done_time new file mode 100644 index 0000000..8397ea2 --- /dev/null +++ b/f14bd410300d6d8802d873c6b584c4aa/done_time @@ -0,0 +1 @@ +2025-07-18T16:22:31.222641244-06:00 \ No newline at end of file From 303df45659a31771c6a20e2a81d0a817b9e58f61 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:24:41 -0600 Subject: [PATCH 318/489] add comment 192f34c4f21e4c6badc0e52881c83fa3 on issue 7e2a3a59fb6b77403ff1035255367607 --- .../comments/192f34c4f21e4c6badc0e52881c83fa3/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 7e2a3a59fb6b77403ff1035255367607/comments/192f34c4f21e4c6badc0e52881c83fa3/description diff --git a/7e2a3a59fb6b77403ff1035255367607/comments/192f34c4f21e4c6badc0e52881c83fa3/description b/7e2a3a59fb6b77403ff1035255367607/comments/192f34c4f21e4c6badc0e52881c83fa3/description new file mode 100644 index 0000000..995e859 --- /dev/null +++ b/7e2a3a59fb6b77403ff1035255367607/comments/192f34c4f21e4c6badc0e52881c83fa3/description @@ -0,0 +1 @@ +had to update the file structure as well, but this is done, and up in !25 From 1ed46bddb74b314ce31cea7ee8559acdf61c67de Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:24:58 -0600 Subject: [PATCH 319/489] change state of issue 7e2a3a59fb6b77403ff1035255367607, new -> inprogress --- 7e2a3a59fb6b77403ff1035255367607/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 7e2a3a59fb6b77403ff1035255367607/state diff --git a/7e2a3a59fb6b77403ff1035255367607/state b/7e2a3a59fb6b77403ff1035255367607/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/7e2a3a59fb6b77403ff1035255367607/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 45bd910324921fb943237789661da1c8d10d1855 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:26:06 -0600 Subject: [PATCH 320/489] change state of issue 3b35e0324d3e5bf863ef93c2f64a6add, inprogress -> done --- 3b35e0324d3e5bf863ef93c2f64a6add/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3b35e0324d3e5bf863ef93c2f64a6add/state b/3b35e0324d3e5bf863ef93c2f64a6add/state index 505c028..348ebd9 100644 --- a/3b35e0324d3e5bf863ef93c2f64a6add/state +++ b/3b35e0324d3e5bf863ef93c2f64a6add/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From fa199efbcf3be408ea3e2afa76b9a9e4b0edb1d7 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:26:06 -0600 Subject: [PATCH 321/489] set done-time of issue 3b35e0324d3e5bf863ef93c2f64a6add to 2025-07-18 16:26:06.414392436 -06:00 --- 3b35e0324d3e5bf863ef93c2f64a6add/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 3b35e0324d3e5bf863ef93c2f64a6add/done_time diff --git a/3b35e0324d3e5bf863ef93c2f64a6add/done_time b/3b35e0324d3e5bf863ef93c2f64a6add/done_time new file mode 100644 index 0000000..c710694 --- /dev/null +++ b/3b35e0324d3e5bf863ef93c2f64a6add/done_time @@ -0,0 +1 @@ +2025-07-18T16:26:06.414392436-06:00 \ No newline at end of file From 96e4e1f1c6b0f72d9c7e572806c238b27017810e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:26:16 -0600 Subject: [PATCH 322/489] change assignee of issue 7e2a3a59fb6b77403ff1035255367607, None -> sigil-03 --- 7e2a3a59fb6b77403ff1035255367607/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 7e2a3a59fb6b77403ff1035255367607/assignee diff --git a/7e2a3a59fb6b77403ff1035255367607/assignee b/7e2a3a59fb6b77403ff1035255367607/assignee new file mode 100644 index 0000000..284bb3b --- /dev/null +++ b/7e2a3a59fb6b77403ff1035255367607/assignee @@ -0,0 +1 @@ +sigil-03 \ No newline at end of file From d8d12997e022bc0fbdbb7e4a4ed4b35f4a22cad0 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:28:09 -0600 Subject: [PATCH 323/489] create new issue cb41626330c86b0d67690022266212e4 --- cb41626330c86b0d67690022266212e4/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 cb41626330c86b0d67690022266212e4/description diff --git a/cb41626330c86b0d67690022266212e4/description b/cb41626330c86b0d67690022266212e4/description new file mode 100644 index 0000000..7f9a503 --- /dev/null +++ b/cb41626330c86b0d67690022266212e4/description @@ -0,0 +1,3 @@ +add updates screen to TUI + +updates pane will allow user to see all updates that occurred on a refresh (with a preview maybe?) and mark them as read From cf2750fb717eacc49c50c8b7a9ca00c259878b32 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:28:22 -0600 Subject: [PATCH 324/489] issue cb41626330c86b0d67690022266212e4 add tag tui --- cb41626330c86b0d67690022266212e4/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 cb41626330c86b0d67690022266212e4/tags diff --git a/cb41626330c86b0d67690022266212e4/tags b/cb41626330c86b0d67690022266212e4/tags new file mode 100644 index 0000000..ba0da07 --- /dev/null +++ b/cb41626330c86b0d67690022266212e4/tags @@ -0,0 +1 @@ +tui From 1412e7b0cf09ca379227452f31697e28cbb64ff1 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:28:29 -0600 Subject: [PATCH 325/489] issue 5e1a860b3ab12ee297492d70d68711d8 add tag tui --- 5e1a860b3ab12ee297492d70d68711d8/tags | 1 + 1 file changed, 1 insertion(+) diff --git a/5e1a860b3ab12ee297492d70d68711d8/tags b/5e1a860b3ab12ee297492d70d68711d8/tags index 6795335..3813c94 100644 --- a/5e1a860b3ab12ee297492d70d68711d8/tags +++ b/5e1a860b3ab12ee297492d70d68711d8/tags @@ -1 +1,2 @@ +tui tui/v0.1 From bfbe3514fa1b10c1f6e2325b5f7a593c32997e45 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:28:34 -0600 Subject: [PATCH 326/489] issue 88d5111fd6e59802d0b839ff1fd6bf71 add tag tui --- 88d5111fd6e59802d0b839ff1fd6bf71/tags | 1 + 1 file changed, 1 insertion(+) diff --git a/88d5111fd6e59802d0b839ff1fd6bf71/tags b/88d5111fd6e59802d0b839ff1fd6bf71/tags index 6795335..3813c94 100644 --- a/88d5111fd6e59802d0b839ff1fd6bf71/tags +++ b/88d5111fd6e59802d0b839ff1fd6bf71/tags @@ -1 +1,2 @@ +tui tui/v0.1 From e00d89442fb17294b2c7bc117f3b4bcc71bd7851 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:28:40 -0600 Subject: [PATCH 327/489] issue bd00a62f9d7c77fd8dd0da5d20aa803d add tag tui --- bd00a62f9d7c77fd8dd0da5d20aa803d/tags | 1 + 1 file changed, 1 insertion(+) diff --git a/bd00a62f9d7c77fd8dd0da5d20aa803d/tags b/bd00a62f9d7c77fd8dd0da5d20aa803d/tags index 6795335..3813c94 100644 --- a/bd00a62f9d7c77fd8dd0da5d20aa803d/tags +++ b/bd00a62f9d7c77fd8dd0da5d20aa803d/tags @@ -1 +1,2 @@ +tui tui/v0.1 From 7622195fd59db05061913a618529039d173ab3fc Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:28:46 -0600 Subject: [PATCH 328/489] issue bdf44bd55fdecdfc1badcb7123d2a606 add tag tui --- bdf44bd55fdecdfc1badcb7123d2a606/tags | 1 + 1 file changed, 1 insertion(+) diff --git a/bdf44bd55fdecdfc1badcb7123d2a606/tags b/bdf44bd55fdecdfc1badcb7123d2a606/tags index 6795335..3813c94 100644 --- a/bdf44bd55fdecdfc1badcb7123d2a606/tags +++ b/bdf44bd55fdecdfc1badcb7123d2a606/tags @@ -1 +1,2 @@ +tui tui/v0.1 From 0ca74168a8eb363e9749c1b66c38196979f26fa5 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:28:52 -0600 Subject: [PATCH 329/489] issue cc6607ad7565902b149e9836dd4f029c add tag tui --- cc6607ad7565902b149e9836dd4f029c/tags | 1 + 1 file changed, 1 insertion(+) diff --git a/cc6607ad7565902b149e9836dd4f029c/tags b/cc6607ad7565902b149e9836dd4f029c/tags index 6795335..3813c94 100644 --- a/cc6607ad7565902b149e9836dd4f029c/tags +++ b/cc6607ad7565902b149e9836dd4f029c/tags @@ -1 +1,2 @@ +tui tui/v0.1 From 4848e953d2e88002b662dad7766888320755ba97 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:36:54 -0600 Subject: [PATCH 330/489] create new issue 87fa3146b90db61c4ea0de182798a0e5 --- 87fa3146b90db61c4ea0de182798a0e5/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 87fa3146b90db61c4ea0de182798a0e5/description diff --git a/87fa3146b90db61c4ea0de182798a0e5/description b/87fa3146b90db61c4ea0de182798a0e5/description new file mode 100644 index 0000000..fb01efe --- /dev/null +++ b/87fa3146b90db61c4ea0de182798a0e5/description @@ -0,0 +1 @@ +large ent repositories are slow From d652f765843930df3c7ed6771728e554ec55b980 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:37:42 -0600 Subject: [PATCH 331/489] create new issue 478ac34c204be06b1da5b4f0b5a2532d --- 478ac34c204be06b1da5b4f0b5a2532d/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 478ac34c204be06b1da5b4f0b5a2532d/description diff --git a/478ac34c204be06b1da5b4f0b5a2532d/description b/478ac34c204be06b1da5b4f0b5a2532d/description new file mode 100644 index 0000000..1b771b3 --- /dev/null +++ b/478ac34c204be06b1da5b4f0b5a2532d/description @@ -0,0 +1 @@ +profile ent to determine where slowdowns occur From 5d427c18b7eacee4ff010f78aae99d48218b4ab5 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:38:10 -0600 Subject: [PATCH 332/489] add dep 478ac34c204be06b1da5b4f0b5a2532d to issue 87fa3146b90db61c4ea0de182798a0e5 --- .../dependencies/478ac34c204be06b1da5b4f0b5a2532d | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 87fa3146b90db61c4ea0de182798a0e5/dependencies/478ac34c204be06b1da5b4f0b5a2532d diff --git a/87fa3146b90db61c4ea0de182798a0e5/dependencies/478ac34c204be06b1da5b4f0b5a2532d b/87fa3146b90db61c4ea0de182798a0e5/dependencies/478ac34c204be06b1da5b4f0b5a2532d new file mode 100644 index 0000000..e69de29 From eb5a6772c5a2453c1c5dabfd269123e623fab23c Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:46:28 -0600 Subject: [PATCH 333/489] create new issue 4a9118e5e06956e0b0766ace15174297 --- 4a9118e5e06956e0b0766ace15174297/description | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 4a9118e5e06956e0b0766ace15174297/description diff --git a/4a9118e5e06956e0b0766ace15174297/description b/4a9118e5e06956e0b0766ace15174297/description new file mode 100644 index 0000000..ffe685f --- /dev/null +++ b/4a9118e5e06956e0b0766ace15174297/description @@ -0,0 +1,8 @@ +add dependency count to `ent list` + + +candidate emojis: +↖ - represents the "dependency tree" DAG edge that points to this issue +⌛- shows that the issue is waiting on something else +🔗- shows that the issue is linked to something else + From 25e656d0ba6425e9c076b356f869553078fee255 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 18 Jul 2025 16:47:45 -0600 Subject: [PATCH 334/489] add comment b65c9fd75be26d9153bbafb07ca1fc61 on issue 4a9118e5e06956e0b0766ace15174297 --- .../comments/b65c9fd75be26d9153bbafb07ca1fc61/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 4a9118e5e06956e0b0766ace15174297/comments/b65c9fd75be26d9153bbafb07ca1fc61/description diff --git a/4a9118e5e06956e0b0766ace15174297/comments/b65c9fd75be26d9153bbafb07ca1fc61/description b/4a9118e5e06956e0b0766ace15174297/comments/b65c9fd75be26d9153bbafb07ca1fc61/description new file mode 100644 index 0000000..7d11ed2 --- /dev/null +++ b/4a9118e5e06956e0b0766ace15174297/comments/b65c9fd75be26d9153bbafb07ca1fc61/description @@ -0,0 +1 @@ +i think my preference is ↖ since it more concisely represents that this issue is waiting on other issues in the DAG / flow... From 1c2f42b0e8f189425e3b65e196ef58008a20b58f Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 19 Jul 2025 00:25:44 -0600 Subject: [PATCH 335/489] create new issue cc7c13a6220f963436dcf29274dc45c5 --- cc7c13a6220f963436dcf29274dc45c5/description | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 cc7c13a6220f963436dcf29274dc45c5/description diff --git a/cc7c13a6220f963436dcf29274dc45c5/description b/cc7c13a6220f963436dcf29274dc45c5/description new file mode 100644 index 0000000..316867e --- /dev/null +++ b/cc7c13a6220f963436dcf29274dc45c5/description @@ -0,0 +1,12 @@ +changes to the ent filesystem break older versions of ent + + +recently with the dependency API, i updated the ent DB with an entry which contains a dependency, and has an updated method of storing them (files inside of a directory instead of a file with a list of dependencies) + +this has caused a problem with mainline `ent` showing the following error: + +because mainline `ent` expects there to be a file with a list of dependencies, and when it goes to open the file, it errors, since it's actually a directory with the same name as what it expects the file to be named. + +we should probably make it so if `ent` doesn't understand something, it does not completely exit... at least in some cases? + +or enforce some kind of versioning on the DB or something... not entirely sure. From ef24689bb8805e35debabbb06b9fd49372b9c9fa Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 19 Jul 2025 00:28:12 -0600 Subject: [PATCH 336/489] create new issue 95a8190f4bbdcafbb4c72db81dfc2aa6 --- 95a8190f4bbdcafbb4c72db81dfc2aa6/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 95a8190f4bbdcafbb4c72db81dfc2aa6/description diff --git a/95a8190f4bbdcafbb4c72db81dfc2aa6/description b/95a8190f4bbdcafbb4c72db81dfc2aa6/description new file mode 100644 index 0000000..38a3aca --- /dev/null +++ b/95a8190f4bbdcafbb4c72db81dfc2aa6/description @@ -0,0 +1,3 @@ +lazy read of DB + +don't remember off the top of my head what this one was whoops From dd2644e12be8559530d794499b2cdc07d4bc540b Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 19 Jul 2025 00:30:37 -0600 Subject: [PATCH 337/489] create new issue b62c30d419fb7727d2faaee20dfed1be --- b62c30d419fb7727d2faaee20dfed1be/description | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 b62c30d419fb7727d2faaee20dfed1be/description diff --git a/b62c30d419fb7727d2faaee20dfed1be/description b/b62c30d419fb7727d2faaee20dfed1be/description new file mode 100644 index 0000000..19ac498 --- /dev/null +++ b/b62c30d419fb7727d2faaee20dfed1be/description @@ -0,0 +1,5 @@ +decrease write window scope + +decrease the amount of time that the `ent` binary has a mutable reference to the database object by only checking out a worktree on a commit operation. + +alternatively (or maybe in addition to?) possibly use branching to allow for simultaneous edits in a safer way. From 5b6f8ad531106f57df2443421c8d9113d6b2a1fe Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 19 Jul 2025 00:32:04 -0600 Subject: [PATCH 338/489] edit description of issue 95a8190f4bbdcafbb4c72db81dfc2aa6 --- 95a8190f4bbdcafbb4c72db81dfc2aa6/description | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/95a8190f4bbdcafbb4c72db81dfc2aa6/description b/95a8190f4bbdcafbb4c72db81dfc2aa6/description index 38a3aca..58a4f48 100644 --- a/95a8190f4bbdcafbb4c72db81dfc2aa6/description +++ b/95a8190f4bbdcafbb4c72db81dfc2aa6/description @@ -1,3 +1,3 @@ lazy read of DB -don't remember off the top of my head what this one was whoops +when running `list` don't load the entire object, instead only load the relevant metadata used to display in the list From 2ba13ebaeb27189fddfb44f897242cd577d9d33e Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 09:53:36 -0600 Subject: [PATCH 339/489] Issue: get rid of all unwraps Make and return errors instead. --- src/issue.rs | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/issue.rs b/src/issue.rs index bc3d959..e129dff 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -153,9 +153,9 @@ impl Issue { } } - if description == None { + let Some(description) = description else { return Err(IssueError::IssueParseError); - } + }; // parse the issue ID from the directory name let id = if let Some(parsed_id) = match dir.file_name() { @@ -179,7 +179,7 @@ impl Issue { state: state, dependencies, assignee, - description: description.unwrap(), + description, comments, dir: std::path::PathBuf::from(dir), }) @@ -267,9 +267,9 @@ impl Issue { "edit description of issue {}", description_filename .parent() - .unwrap() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))? .file_name() - .unwrap() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))? .to_string_lossy(), ))?; Ok(()) @@ -293,7 +293,10 @@ impl Issue { write!(state_file, "{}", new_state)?; self.commit(&format!( "change state of issue {}, {} -> {}", - self.dir.file_name().unwrap().to_string_lossy(), + self.dir + .file_name() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))? + .to_string_lossy(), old_state, new_state, ))?; @@ -323,7 +326,10 @@ impl Issue { self.done_time = Some(done_time.clone()); self.commit(&format!( "set done-time of issue {} to {}", - self.dir.file_name().unwrap().to_string_lossy(), + self.dir + .file_name() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))? + .to_string_lossy(), done_time, ))?; Ok(()) @@ -341,7 +347,10 @@ impl Issue { write!(assignee_file, "{}", new_assignee)?; self.commit(&format!( "change assignee of issue {}, {} -> {}", - self.dir.file_name().unwrap().to_string_lossy(), + self.dir + .file_name() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))? + .to_string_lossy(), old_assignee, new_assignee, ))?; @@ -358,7 +367,10 @@ impl Issue { self.tags.sort(); self.commit_tags(&format!( "issue {} add tag {}", - self.dir.file_name().unwrap().to_string_lossy(), + self.dir + .file_name() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))? + .to_string_lossy(), tag ))?; Ok(()) @@ -373,7 +385,10 @@ impl Issue { self.tags.remove(index); self.commit_tags(&format!( "issue {} remove tag {}", - self.dir.file_name().unwrap().to_string_lossy(), + self.dir + .file_name() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))? + .to_string_lossy(), tag ))?; Ok(()) @@ -432,8 +447,8 @@ impl Issue { .spawn()? .wait_with_output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(IssueError::EditorError); } if !description_filename.exists() || description_filename.metadata()?.len() == 0 { From 97a575316e4af91300b6c0414e5f3160578b3691 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 09:54:00 -0600 Subject: [PATCH 340/489] Issues: skip & warn about any Issue that fails to parse This lets us at least handle the other, valid issues, while informing the user about the ones we don't understand. --- src/issues.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/issues.rs b/src/issues.rs index 709a79d..fe5728c 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -56,8 +56,19 @@ impl Issues { for direntry in dir.read_dir()? { if let Ok(direntry) = direntry { if direntry.metadata()?.is_dir() { - let issue = crate::issue::Issue::new_from_dir(direntry.path().as_path())?; - issues.add_issue(issue); + match crate::issue::Issue::new_from_dir(direntry.path().as_path()) { + Err(e) => { + println!( + "failed to parse issue {}, skipping", + direntry.file_name().to_string_lossy() + ); + println!("ignoring error: {:?}", e); + continue; + } + Ok(issue) => { + issues.add_issue(issue); + } + } } else if direntry.file_name() == "config.toml" { issues.parse_config(direntry.path().as_path())?; } else { From c2174340711ad4628dea5b8899c93d48cb5c406f Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 10:38:15 -0600 Subject: [PATCH 341/489] better error handling in comment and git This replaces a bunch of `unwrap()` calls with error returns. --- src/comment.rs | 31 ++++++++---- src/git.rs | 125 +++++++++++++++++++++++++++++-------------------- 2 files changed, 96 insertions(+), 60 deletions(-) diff --git a/src/comment.rs b/src/comment.rs index 216b34f..17324b3 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -48,19 +48,23 @@ impl Comment { } } } - if description == None { + let Some(description) = description else { 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 dir = std::path::PathBuf::from(comment_dir); Ok(Self { - uuid: String::from(dir.file_name().unwrap().to_string_lossy()), + uuid: String::from( + dir.file_name() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))? + .to_string_lossy(), + ), author, creation_time, - description: description.unwrap(), + description, dir: std::path::PathBuf::from(comment_dir), }) } @@ -109,7 +113,11 @@ impl Comment { &format!( "add comment {} on issue {}", comment.uuid, - issue.dir.file_name().unwrap().to_string_lossy(), + issue + .dir + .file_name() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))? + .to_string_lossy(), ), )?; } @@ -130,10 +138,15 @@ impl Comment { crate::git::add(&description_filename)?; if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { crate::git::commit( - &description_filename.parent().unwrap(), + &description_filename + .parent() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))?, &format!( "edit comment {} on issue FIXME", // FIXME: name the issue that the comment is on - self.dir.file_name().unwrap().to_string_lossy() + self.dir + .file_name() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))? + .to_string_lossy() ), )?; self.read_description()?; @@ -165,8 +178,8 @@ impl Comment { .spawn()? .wait_with_output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(CommentError::EditorError); } diff --git a/src/git.rs b/src/git.rs index fe0446a..4a50d57 100644 --- a/src/git.rs +++ b/src/git.rs @@ -48,8 +48,8 @@ impl Worktree { .args(["worktree", "add", &path.path().to_string_lossy(), branch]) .output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } Ok(Self { path }) @@ -67,8 +67,8 @@ impl Worktree { ]) .output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } Ok(Self { path }) @@ -87,8 +87,8 @@ pub fn checkout_branch_in_worktree( .args(["worktree", "add", &worktree_dir.to_string_lossy(), branch]) .output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } Ok(()) @@ -99,8 +99,8 @@ pub fn git_worktree_prune() -> Result<(), GitError> { .args(["worktree", "prune"]) .output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } Ok(()) @@ -111,8 +111,8 @@ pub fn git_remove_branch(branch: &str) -> Result<(), GitError> { .args(["branch", "-D", branch]) .output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } Ok(()) @@ -139,11 +139,14 @@ pub fn worktree_is_dirty(dir: &str) -> Result { pub fn add(file: &std::path::Path) -> Result<(), GitError> { let result = std::process::Command::new("git") .args(["add", &file.to_string_lossy()]) - .current_dir(file.parent().unwrap()) + .current_dir( + file.parent() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))?, + ) .output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } return Ok(()); @@ -152,11 +155,14 @@ pub fn add(file: &std::path::Path) -> Result<(), GitError> { pub fn restore_file(file: &std::path::Path) -> Result<(), GitError> { let result = std::process::Command::new("git") .args(["restore", &file.to_string_lossy()]) - .current_dir(file.parent().unwrap()) + .current_dir( + file.parent() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))?, + ) .output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } return Ok(()); @@ -168,8 +174,8 @@ pub fn commit(dir: &std::path::Path, msg: &str) -> Result<(), GitError> { .current_dir(dir) .output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } Ok(()) @@ -180,12 +186,18 @@ pub fn git_commit_file(file: &std::path::Path) -> Result<(), GitError> { git_dir.pop(); let result = std::process::Command::new("git") - .args(["add", &file.file_name().unwrap().to_string_lossy()]) + .args([ + "add", + &file + .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: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } @@ -195,15 +207,20 @@ pub fn git_commit_file(file: &std::path::Path) -> Result<(), GitError> { "-m", &format!( "update '{}' in issue {}", - file.file_name().unwrap().to_string_lossy(), - git_dir.file_name().unwrap().to_string_lossy() + file.file_name() + .ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))? + .to_string_lossy(), + git_dir + .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: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } @@ -216,8 +233,8 @@ pub fn git_fetch(dir: &std::path::Path, remote: &str) -> Result<(), GitError> { .current_dir(dir) .output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } Ok(()) @@ -253,13 +270,13 @@ pub fn sync(dir: &std::path::Path, remote: &str, branch: &str) -> Result<(), Git "Sync failed! 'git log' error! Help, a human needs to fix the mess in {:?}", branch ); - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } if result.stdout.len() > 0 { println!("Changes fetched from remote {}:", remote); - println!("{}", std::str::from_utf8(&result.stdout).unwrap()); + println!("{}", &String::from_utf8_lossy(&result.stdout)); println!(""); } @@ -279,13 +296,13 @@ pub fn sync(dir: &std::path::Path, remote: &str, branch: &str) -> Result<(), Git "Sync failed! 'git log' error! Help, a human needs to fix the mess in {:?}", branch ); - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } if result.stdout.len() > 0 { println!("Changes to push to remote {}:", remote); - println!("{}", std::str::from_utf8(&result.stdout).unwrap()); + println!("{}", &String::from_utf8_lossy(&result.stdout)); println!(""); } @@ -299,8 +316,8 @@ pub fn sync(dir: &std::path::Path, remote: &str, branch: &str) -> Result<(), Git "Sync failed! Merge error! Help, a human needs to fix the mess in {:?}", branch ); - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } @@ -314,8 +331,8 @@ pub fn sync(dir: &std::path::Path, remote: &str, branch: &str) -> Result<(), Git "Sync failed! Push error! Help, a human needs to fix the mess in {:?}", branch ); - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } @@ -332,13 +349,16 @@ pub fn git_log_oldest_timestamp( "log", "--pretty=format:%at", "--", - &path.file_name().unwrap().to_string_lossy(), + &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: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } let timestamp_str = std::str::from_utf8(&result.stdout).unwrap(); @@ -358,13 +378,16 @@ pub fn git_log_oldest_author(path: &std::path::Path) -> Result "log", "--pretty=format:%an <%ae>", "--", - &path.file_name().unwrap().to_string_lossy(), + &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: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } let author_str = std::str::from_utf8(&result.stdout).unwrap(); @@ -383,8 +406,8 @@ pub fn create_orphan_branch(branch: &str) -> Result<(), GitError> { .args(["worktree", "prune"]) .output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } @@ -400,8 +423,8 @@ fn create_orphan_branch_at_path( .args(["worktree", "add", "--orphan", "-b", branch, &worktree_dir]) .output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } @@ -418,8 +441,8 @@ fn create_orphan_branch_at_path( .current_dir(worktree_path) .output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } @@ -428,8 +451,8 @@ fn create_orphan_branch_at_path( .current_dir(worktree_path) .output()?; if !result.status.success() { - println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); - println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + println!("stdout: {}", &String::from_utf8_lossy(&result.stdout)); + println!("stderr: {}", &String::from_utf8_lossy(&result.stderr)); return Err(GitError::Oops); } From ed8c62548d0ddf10578579b93747bd5b57621c98 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 10:49:32 -0600 Subject: [PATCH 342/489] create new issue 000ddc5eaf58e781c945d6321970ec40 --- 000ddc5eaf58e781c945d6321970ec40/description | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 000ddc5eaf58e781c945d6321970ec40/description diff --git a/000ddc5eaf58e781c945d6321970ec40/description b/000ddc5eaf58e781c945d6321970ec40/description new file mode 100644 index 0000000..35ddce6 --- /dev/null +++ b/000ddc5eaf58e781c945d6321970ec40/description @@ -0,0 +1,8 @@ +add an option to `ent list` to just show filtered issue ids + +This would be useful in scripts, like: +``` +for ISSUE_ID in $(ent list --ids-only ${MY_FILTER}); do + # do thing with ${ISSUE_ID} +done +``` From 03c1ac3bb396e80317fc8ce10fb5effd39cfa48c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 10:52:01 -0600 Subject: [PATCH 343/489] edit description of issue 000ddc5eaf58e781c945d6321970ec40 --- 000ddc5eaf58e781c945d6321970ec40/description | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/000ddc5eaf58e781c945d6321970ec40/description b/000ddc5eaf58e781c945d6321970ec40/description index 35ddce6..2792f4b 100644 --- a/000ddc5eaf58e781c945d6321970ec40/description +++ b/000ddc5eaf58e781c945d6321970ec40/description @@ -1,4 +1,4 @@ -add an option to `ent list` to just show filtered issue ids +add an option to `ent list` to show only filtered issue ids This would be useful in scripts, like: ``` From e79fc4917d703b71a9f04431bf1b667079af0aef Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 10:52:33 -0600 Subject: [PATCH 344/489] Issues::new_from_dir(): move error message to stderr --- src/issues.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/issues.rs b/src/issues.rs index fe5728c..a01f41c 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -58,11 +58,11 @@ impl Issues { if direntry.metadata()?.is_dir() { match crate::issue::Issue::new_from_dir(direntry.path().as_path()) { Err(e) => { - println!( + eprintln!( "failed to parse issue {}, skipping", direntry.file_name().to_string_lossy() ); - println!("ignoring error: {:?}", e); + eprintln!("ignoring error: {:?}", e); continue; } Ok(issue) => { From 5ef9bcbb502fdc018b0f9bebbb08313c1aaa6255 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 10:53:55 -0600 Subject: [PATCH 345/489] edit description of issue 000ddc5eaf58e781c945d6321970ec40 --- 000ddc5eaf58e781c945d6321970ec40/description | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/000ddc5eaf58e781c945d6321970ec40/description b/000ddc5eaf58e781c945d6321970ec40/description index 2792f4b..cc07c5e 100644 --- a/000ddc5eaf58e781c945d6321970ec40/description +++ b/000ddc5eaf58e781c945d6321970ec40/description @@ -6,3 +6,8 @@ for ISSUE_ID in $(ent list --ids-only ${MY_FILTER}); do # do thing with ${ISSUE_ID} done ``` + +For now you can sort of emulate this with: +``` +ent list state=done done-time=2026-01-01T00:00:00-06:00.. | grep ' ' | cut -f 1 -d ' ' +``` From e2613eb77a440cb9ddc9af0e56406a9295084bd5 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:02:15 -0600 Subject: [PATCH 346/489] add comment 0c39fa626ffff7e2f3a9b704e2eb7ec7 on issue 08f0d7ee7842c439382816d21ec1dea2 --- .../0c39fa626ffff7e2f3a9b704e2eb7ec7/description | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 08f0d7ee7842c439382816d21ec1dea2/comments/0c39fa626ffff7e2f3a9b704e2eb7ec7/description diff --git a/08f0d7ee7842c439382816d21ec1dea2/comments/0c39fa626ffff7e2f3a9b704e2eb7ec7/description b/08f0d7ee7842c439382816d21ec1dea2/comments/0c39fa626ffff7e2f3a9b704e2eb7ec7/description new file mode 100644 index 0000000..fc05e0d --- /dev/null +++ b/08f0d7ee7842c439382816d21ec1dea2/comments/0c39fa626ffff7e2f3a9b704e2eb7ec7/description @@ -0,0 +1,12 @@ +The done-time thing is implemented and merged, but many issues in our +database were marked Done before this change, so they have no `done_time`. + +I think i want to fix this by setting a done-time for each of them using +something like: +``` +for ISSUE_ID in $(ent list state=done done-time=9999-01-01T00:00:00-06:00.. | grep ' ' | cut -f 1 -d ' '); do + # use `git log` on the issue's state file to find when it got marked Done + ent done-time ${ISSUE_ID} ${TIME} +done +``` + From 40c16c3b55023da4541a2dccb8e39f01f7123433 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:06:16 -0600 Subject: [PATCH 347/489] set done-time of issue e089400e8a9e11fe9bf10d50b2f889d7 to 2025-07-08 16:16:09 -06:00 --- e089400e8a9e11fe9bf10d50b2f889d7/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 e089400e8a9e11fe9bf10d50b2f889d7/done_time diff --git a/e089400e8a9e11fe9bf10d50b2f889d7/done_time b/e089400e8a9e11fe9bf10d50b2f889d7/done_time new file mode 100644 index 0000000..1ca8397 --- /dev/null +++ b/e089400e8a9e11fe9bf10d50b2f889d7/done_time @@ -0,0 +1 @@ +2025-07-08T16:16:09-06:00 \ No newline at end of file From 10728968fe556ea07b3aa816b37d481f0e9cbe56 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:06:21 -0600 Subject: [PATCH 348/489] set done-time of issue a26da230276d317e85f9fcca41c19d2e to 2025-07-10 09:49:17 -06:00 --- a26da230276d317e85f9fcca41c19d2e/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 a26da230276d317e85f9fcca41c19d2e/done_time diff --git a/a26da230276d317e85f9fcca41c19d2e/done_time b/a26da230276d317e85f9fcca41c19d2e/done_time new file mode 100644 index 0000000..9f12039 --- /dev/null +++ b/a26da230276d317e85f9fcca41c19d2e/done_time @@ -0,0 +1 @@ +2025-07-10T09:49:17-06:00 \ No newline at end of file From 554f3fd7c46763589700d23c71e3f33e4dd32587 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:06:25 -0600 Subject: [PATCH 349/489] set done-time of issue 317ea8ccac1d414cde55771321bdec30 to 2025-07-11 11:54:56 -06:00 --- 317ea8ccac1d414cde55771321bdec30/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 317ea8ccac1d414cde55771321bdec30/done_time diff --git a/317ea8ccac1d414cde55771321bdec30/done_time b/317ea8ccac1d414cde55771321bdec30/done_time new file mode 100644 index 0000000..be44435 --- /dev/null +++ b/317ea8ccac1d414cde55771321bdec30/done_time @@ -0,0 +1 @@ +2025-07-11T11:54:56-06:00 \ No newline at end of file From de772691b6ed2bf2e2208529ec550e9e126e42c1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:06:30 -0600 Subject: [PATCH 350/489] set done-time of issue da435e5e298b28dc223f9dcfe62a9140 to 2025-07-07 21:24:25 -06:00 --- da435e5e298b28dc223f9dcfe62a9140/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 da435e5e298b28dc223f9dcfe62a9140/done_time diff --git a/da435e5e298b28dc223f9dcfe62a9140/done_time b/da435e5e298b28dc223f9dcfe62a9140/done_time new file mode 100644 index 0000000..168647e --- /dev/null +++ b/da435e5e298b28dc223f9dcfe62a9140/done_time @@ -0,0 +1 @@ +2025-07-07T21:24:25-06:00 \ No newline at end of file From 08a9644e37dc8c4c30cf66db945b54f905274eb3 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:06:35 -0600 Subject: [PATCH 351/489] set done-time of issue fd81241f795333b64e7911cfb1b57c8f to 2025-07-11 20:32:31 -06:00 --- fd81241f795333b64e7911cfb1b57c8f/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 fd81241f795333b64e7911cfb1b57c8f/done_time diff --git a/fd81241f795333b64e7911cfb1b57c8f/done_time b/fd81241f795333b64e7911cfb1b57c8f/done_time new file mode 100644 index 0000000..e61c147 --- /dev/null +++ b/fd81241f795333b64e7911cfb1b57c8f/done_time @@ -0,0 +1 @@ +2025-07-11T20:32:31-06:00 \ No newline at end of file From a93ebac8120dd5e4e8b81003e05f31e70e3111ca Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:06:39 -0600 Subject: [PATCH 352/489] set done-time of issue 093e87e8049b93bfa2d8fcd544cae75f to 2025-07-08 18:48:13 -06:00 --- 093e87e8049b93bfa2d8fcd544cae75f/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 093e87e8049b93bfa2d8fcd544cae75f/done_time diff --git a/093e87e8049b93bfa2d8fcd544cae75f/done_time b/093e87e8049b93bfa2d8fcd544cae75f/done_time new file mode 100644 index 0000000..98c8b92 --- /dev/null +++ b/093e87e8049b93bfa2d8fcd544cae75f/done_time @@ -0,0 +1 @@ +2025-07-08T18:48:13-06:00 \ No newline at end of file From e847a7e92a40f89236902236ab2bb163186c5382 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:06:44 -0600 Subject: [PATCH 353/489] set done-time of issue 793bda8b9726b0336d97e856895907f8 to 2025-07-08 18:48:23 -06:00 --- 793bda8b9726b0336d97e856895907f8/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 793bda8b9726b0336d97e856895907f8/done_time diff --git a/793bda8b9726b0336d97e856895907f8/done_time b/793bda8b9726b0336d97e856895907f8/done_time new file mode 100644 index 0000000..2dd5851 --- /dev/null +++ b/793bda8b9726b0336d97e856895907f8/done_time @@ -0,0 +1 @@ +2025-07-08T18:48:23-06:00 \ No newline at end of file From eaccd15ccdd738cfd0779855967f29d0e4213d2f Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:06:48 -0600 Subject: [PATCH 354/489] set done-time of issue af53c561b36e9b2709b939f81daee534 to 2025-07-08 18:48:37 -06:00 --- af53c561b36e9b2709b939f81daee534/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 af53c561b36e9b2709b939f81daee534/done_time diff --git a/af53c561b36e9b2709b939f81daee534/done_time b/af53c561b36e9b2709b939f81daee534/done_time new file mode 100644 index 0000000..df6fb87 --- /dev/null +++ b/af53c561b36e9b2709b939f81daee534/done_time @@ -0,0 +1 @@ +2025-07-08T18:48:37-06:00 \ No newline at end of file From de85949c63a288753393f43d8c8f8584946c3621 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:06:53 -0600 Subject: [PATCH 355/489] set done-time of issue 9e69a30ad6965d7488514584c97ac63c to 2025-07-09 11:03:14 -06:00 --- 9e69a30ad6965d7488514584c97ac63c/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 9e69a30ad6965d7488514584c97ac63c/done_time diff --git a/9e69a30ad6965d7488514584c97ac63c/done_time b/9e69a30ad6965d7488514584c97ac63c/done_time new file mode 100644 index 0000000..82b181f --- /dev/null +++ b/9e69a30ad6965d7488514584c97ac63c/done_time @@ -0,0 +1 @@ +2025-07-09T11:03:14-06:00 \ No newline at end of file From bc7672d943d72375554bc325faa82098ee6f030a Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:06:58 -0600 Subject: [PATCH 356/489] set done-time of issue a5ac277614ea4d13f78031abb25ea7d6 to 2025-07-10 09:49:24 -06:00 --- a5ac277614ea4d13f78031abb25ea7d6/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 a5ac277614ea4d13f78031abb25ea7d6/done_time diff --git a/a5ac277614ea4d13f78031abb25ea7d6/done_time b/a5ac277614ea4d13f78031abb25ea7d6/done_time new file mode 100644 index 0000000..359ef30 --- /dev/null +++ b/a5ac277614ea4d13f78031abb25ea7d6/done_time @@ -0,0 +1 @@ +2025-07-10T09:49:24-06:00 \ No newline at end of file From 472ca9e7f6dfbe42e5cf7c9dfffaaa5c6f2d1563 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:07:03 -0600 Subject: [PATCH 357/489] set done-time of issue 7d2d236668872cf11f167ac0462f8751 to 2025-07-12 16:14:46 -06:00 --- 7d2d236668872cf11f167ac0462f8751/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 7d2d236668872cf11f167ac0462f8751/done_time diff --git a/7d2d236668872cf11f167ac0462f8751/done_time b/7d2d236668872cf11f167ac0462f8751/done_time new file mode 100644 index 0000000..b4df3d5 --- /dev/null +++ b/7d2d236668872cf11f167ac0462f8751/done_time @@ -0,0 +1 @@ +2025-07-12T16:14:46-06:00 \ No newline at end of file From f3b6eb931263d0ebdf28a4c073c98a7fb024daf1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:07:07 -0600 Subject: [PATCH 358/489] set done-time of issue 54f0eb67b05aa10763c86869ce840f33 to 2025-07-11 20:32:59 -06:00 --- 54f0eb67b05aa10763c86869ce840f33/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 54f0eb67b05aa10763c86869ce840f33/done_time diff --git a/54f0eb67b05aa10763c86869ce840f33/done_time b/54f0eb67b05aa10763c86869ce840f33/done_time new file mode 100644 index 0000000..9527745 --- /dev/null +++ b/54f0eb67b05aa10763c86869ce840f33/done_time @@ -0,0 +1 @@ +2025-07-11T20:32:59-06:00 \ No newline at end of file From c9300083183ad0c11ededa83685458767c828dd9 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:07:12 -0600 Subject: [PATCH 359/489] set done-time of issue 4e314a8590864fa76d22758e1785ae35 to 2025-07-13 10:39:17 -06:00 --- 4e314a8590864fa76d22758e1785ae35/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 4e314a8590864fa76d22758e1785ae35/done_time diff --git a/4e314a8590864fa76d22758e1785ae35/done_time b/4e314a8590864fa76d22758e1785ae35/done_time new file mode 100644 index 0000000..1fd5373 --- /dev/null +++ b/4e314a8590864fa76d22758e1785ae35/done_time @@ -0,0 +1 @@ +2025-07-13T10:39:17-06:00 \ No newline at end of file From bc3239c72a95f961517d9b9383a308839a929fd0 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 11:07:17 -0600 Subject: [PATCH 360/489] set done-time of issue d3a705245bd69aa56524b80b5ae0bc26 to 2025-07-13 21:56:29 -06:00 --- d3a705245bd69aa56524b80b5ae0bc26/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 d3a705245bd69aa56524b80b5ae0bc26/done_time diff --git a/d3a705245bd69aa56524b80b5ae0bc26/done_time b/d3a705245bd69aa56524b80b5ae0bc26/done_time new file mode 100644 index 0000000..de06dc9 --- /dev/null +++ b/d3a705245bd69aa56524b80b5ae0bc26/done_time @@ -0,0 +1 @@ +2025-07-13T21:56:29-06:00 \ No newline at end of file From 3bfb78aa4be881b98e39460d9d226c554bc91f95 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 12:15:35 -0600 Subject: [PATCH 361/489] edit comment 0c39fa626ffff7e2f3a9b704e2eb7ec7 on issue FIXME --- .../0c39fa626ffff7e2f3a9b704e2eb7ec7/description | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/08f0d7ee7842c439382816d21ec1dea2/comments/0c39fa626ffff7e2f3a9b704e2eb7ec7/description b/08f0d7ee7842c439382816d21ec1dea2/comments/0c39fa626ffff7e2f3a9b704e2eb7ec7/description index fc05e0d..b1c4818 100644 --- a/08f0d7ee7842c439382816d21ec1dea2/comments/0c39fa626ffff7e2f3a9b704e2eb7ec7/description +++ b/08f0d7ee7842c439382816d21ec1dea2/comments/0c39fa626ffff7e2f3a9b704e2eb7ec7/description @@ -1,12 +1,14 @@ The done-time thing is implemented and merged, but many issues in our database were marked Done before this change, so they have no `done_time`. -I think i want to fix this by setting a done-time for each of them using -something like: +I fixed this by setting a done-time for each of them using this script: ``` +#!/bin/bash +set -e + for ISSUE_ID in $(ent list state=done done-time=9999-01-01T00:00:00-06:00.. | grep ' ' | cut -f 1 -d ' '); do - # use `git log` on the issue's state file to find when it got marked Done - ent done-time ${ISSUE_ID} ${TIME} + UTIME=$(PAGER='' git log -n1 --pretty=format:%at%n entomologist-data -- ${ISSUE_ID}/state) + DATETIME=$(date --rfc-3339=seconds --date="@${UTIME}") + ent done-time ${ISSUE_ID} "${DATETIME}" done ``` - From 02ea1d1e6e57aae1b8cb7496e307fa372f973ea2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 12:24:00 -0600 Subject: [PATCH 362/489] add comment bc45a23fdb019a5837711231dbace000 on issue 08f0d7ee7842c439382816d21ec1dea2 --- .../description | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 08f0d7ee7842c439382816d21ec1dea2/comments/bc45a23fdb019a5837711231dbace000/description diff --git a/08f0d7ee7842c439382816d21ec1dea2/comments/bc45a23fdb019a5837711231dbace000/description b/08f0d7ee7842c439382816d21ec1dea2/comments/bc45a23fdb019a5837711231dbace000/description new file mode 100644 index 0000000..c3010a7 --- /dev/null +++ b/08f0d7ee7842c439382816d21ec1dea2/comments/bc45a23fdb019a5837711231dbace000/description @@ -0,0 +1,38 @@ +The normal `date` command is helpful here: +``` +$ date --iso-8601=seconds +2025-07-19T12:22:56-06:00 + +$ date --iso-8601=seconds --date='last monday' +2025-07-14T00:00:00-06:00 + +$ date --iso-8601=seconds --date='last monday - 1 week' +2025-07-07T00:00:00-06:00 +``` + + +Given that, here's a "what did we finish last week" command: +``` +$ ent list state=done done-time=$(date --iso-8601=seconds --date='last monday - 1 week')..$(date --iso-8601=seconds --date='last monday') +failed to parse issue 87fa3146b90db61c4ea0de182798a0e5, skipping +ignoring error: StdIoError(Os { code: 21, kind: IsADirectory, message: "Is a directory" }) +Done: +8c73c9fd5bc4f551ee5069035ae6e866 migrate the Todo list into entomologist +75cefad80aacbf23fc7b9c24a75aa236 🗨️ 4 # implement `ent comment ISSUE [DESCRIPTION]` (👉 seb) +7da3bd5b72de0a05936b094db5d24304 🗨️ 1 implement `ent edit ${COMMENT}` (👉 seb) +198a7d56a19f0579fbc04f2ee9cc234f fix ignoring unknown file in issues directory: "README.md" +e089400e8a9e11fe9bf10d50b2f889d7 add `ent sync` to keep local `entomologist-data` branch in sync with remote +a26da230276d317e85f9fcca41c19d2e `ent edit ${ISSUE}` with no change fails (👉 seb) +317ea8ccac1d414cde55771321bdec30 🗨️ 2 allow multiple read-only ent processes simultaneously (👉 seb) +da435e5e298b28dc223f9dcfe62a9140 add user control over state transitions (👉 lex) +fd81241f795333b64e7911cfb1b57c8f commit messages in the `entomologist-data` branch could be better (👉 seb) +093e87e8049b93bfa2d8fcd544cae75f add optional 'assignee' to issue (👉 seb) +793bda8b9726b0336d97e856895907f8 `ent list` should have a consistent sort order (👉 seb) +af53c561b36e9b2709b939f81daee534 use git author info to attribute issues and comments to people (👉 seb) +9e69a30ad6965d7488514584c97ac63c teach `ent list FILTER` to filter by assignee (👉 seb) +a5ac277614ea4d13f78031abb25ea7d6 `ent new` and `ent comment`: detect empty issue descriptions & comments (👉 seb) +7d2d236668872cf11f167ac0462f8751 🗨️ 1 add `ent tag ISSUE [[-]TAG]` (👉 seb) +54f0eb67b05aa10763c86869ce840f33 `ent sync` should report what changes got fetched & what changes will be pushed (👉 seb) +4e314a8590864fa76d22758e1785ae35 don't spawn an editor if stdin & stdout aren't a terminal (👉 seb) +d3a705245bd69aa56524b80b5ae0bc26 🗨️ 1 move IssuesDatabase out of binary and into library (👉 sigil-03) +``` From 8b41f1ebc67a33a3fd6a8cc6c8522bd2a82ece70 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 13:15:50 -0600 Subject: [PATCH 363/489] add a tools directory including a "done-last-week" script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This script lists issues that were marked `Done` between "midnight at the start of the second most recent Monday" and "midnight at the start of the most recent Monday". $ ./tools/done-last-week Done: 8c73c9fd5bc4f551ee5069035ae6e866 migrate the Todo list into entomologist 75cefad80aacbf23fc7b9c24a75aa236 🗨️ 4 # implement `ent comment ISSUE [DESCRIPTION]` (👉 seb) 7da3bd5b72de0a05936b094db5d24304 🗨️ 1 implement `ent edit ${COMMENT}` (👉 seb) 198a7d56a19f0579fbc04f2ee9cc234f fix ignoring unknown file in issues directory: "README.md" e089400e8a9e11fe9bf10d50b2f889d7 add `ent sync` to keep local `entomologist-data` branch in sync with remote a26da230276d317e85f9fcca41c19d2e `ent edit ${ISSUE}` with no change fails (👉 seb) 317ea8ccac1d414cde55771321bdec30 🗨️ 2 allow multiple read-only ent processes simultaneously (👉 seb) da435e5e298b28dc223f9dcfe62a9140 add user control over state transitions (👉 lex) fd81241f795333b64e7911cfb1b57c8f commit messages in the `entomologist-data` branch could be better (👉 seb) 093e87e8049b93bfa2d8fcd544cae75f add optional 'assignee' to issue (👉 seb) 793bda8b9726b0336d97e856895907f8 `ent list` should have a consistent sort order (👉 seb) af53c561b36e9b2709b939f81daee534 use git author info to attribute issues and comments to people (👉 seb) 9e69a30ad6965d7488514584c97ac63c teach `ent list FILTER` to filter by assignee (👉 seb) a5ac277614ea4d13f78031abb25ea7d6 `ent new` and `ent comment`: detect empty issue descriptions & comments (👉 seb) 7d2d236668872cf11f167ac0462f8751 🗨️ 1 add `ent tag ISSUE [[-]TAG]` (👉 seb) 54f0eb67b05aa10763c86869ce840f33 `ent sync` should report what changes got fetched & what changes will be pushed (👉 seb) 4e314a8590864fa76d22758e1785ae35 don't spawn an editor if stdin & stdout aren't a terminal (👉 seb) d3a705245bd69aa56524b80b5ae0bc26 🗨️ 1 move IssuesDatabase out of binary and into library (👉 sigil-03) --- tools/README.md | 4 ++++ tools/done-last-week | 11 +++++++++++ tools/set-done-time | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 tools/README.md create mode 100755 tools/done-last-week create mode 100755 tools/set-done-time diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000..dd3cb2f --- /dev/null +++ b/tools/README.md @@ -0,0 +1,4 @@ +This directory contains small helper scripts and tools that are peripheral +or tangent to the main entomologist tool. + +We make no guarantees about functionality or correctness. diff --git a/tools/done-last-week b/tools/done-last-week new file mode 100755 index 0000000..da9ec94 --- /dev/null +++ b/tools/done-last-week @@ -0,0 +1,11 @@ +#!/bin/bash + +START=$(date --iso-8601=seconds --date='last monday - 1 week') +END=$(date --iso-8601=seconds --date='last monday') + +#echo START=${START} +#echo END=${END} + +ent list \ + state=done \ + done-time="${START}..${END}" diff --git a/tools/set-done-time b/tools/set-done-time new file mode 100755 index 0000000..6a29fd9 --- /dev/null +++ b/tools/set-done-time @@ -0,0 +1,19 @@ +#!/bin/bash +# +# This script finds all issues with state=Done which do not have a +# `done_time`. +# +# It sets each issue's `done_time` to the most recent time that the +# `state` was updated from the git log. +# + +set -e + +for ISSUE_ID in $(ent list state=done done-time=9999-01-01T00:00:00-06:00.. | grep ' ' | cut -f 1 -d ' '); do + echo ${ISSUE_ID} + UTIME=$(PAGER='' git log -n1 --pretty=format:%at%n entomologist-data -- ${ISSUE_ID}/state) + echo ${UTIME} + DATETIME=$(date --rfc-3339=seconds --date="@${UTIME}") + echo ${DATETIME} + ent done-time ${ISSUE_ID} "${DATETIME}" +done From 0ea3211478b03f6ebbec80c2619c1bc5342e93c6 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:31:35 -0600 Subject: [PATCH 364/489] change state of issue 08f0d7ee7842c439382816d21ec1dea2, inprogress -> done --- 08f0d7ee7842c439382816d21ec1dea2/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/08f0d7ee7842c439382816d21ec1dea2/state b/08f0d7ee7842c439382816d21ec1dea2/state index 505c028..348ebd9 100644 --- a/08f0d7ee7842c439382816d21ec1dea2/state +++ b/08f0d7ee7842c439382816d21ec1dea2/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From c5fa0752d281a73dfae4d433e91aab631a410c26 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:31:35 -0600 Subject: [PATCH 365/489] set done-time of issue 08f0d7ee7842c439382816d21ec1dea2 to 2025-07-19 20:31:35.010603718 -06:00 --- 08f0d7ee7842c439382816d21ec1dea2/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 08f0d7ee7842c439382816d21ec1dea2/done_time diff --git a/08f0d7ee7842c439382816d21ec1dea2/done_time b/08f0d7ee7842c439382816d21ec1dea2/done_time new file mode 100644 index 0000000..198df67 --- /dev/null +++ b/08f0d7ee7842c439382816d21ec1dea2/done_time @@ -0,0 +1 @@ +2025-07-19T20:31:35.010603718-06:00 \ No newline at end of file From f381accc4daa97325ae697b6cf71da6a76754278 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:33:03 -0600 Subject: [PATCH 366/489] edit description of issue 000ddc5eaf58e781c945d6321970ec40 --- 000ddc5eaf58e781c945d6321970ec40/description | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/000ddc5eaf58e781c945d6321970ec40/description b/000ddc5eaf58e781c945d6321970ec40/description index cc07c5e..8e7da78 100644 --- a/000ddc5eaf58e781c945d6321970ec40/description +++ b/000ddc5eaf58e781c945d6321970ec40/description @@ -1,4 +1,4 @@ -add an option to `ent list` to show only filtered issue ids +add an option to `ent list` to show just the ids of filtered issues This would be useful in scripts, like: ``` From 663d399b38f7285c4b54d003f6d62522791da4de Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:33:13 -0600 Subject: [PATCH 367/489] change state of issue 000ddc5eaf58e781c945d6321970ec40, new -> backlog --- 000ddc5eaf58e781c945d6321970ec40/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 000ddc5eaf58e781c945d6321970ec40/state diff --git a/000ddc5eaf58e781c945d6321970ec40/state b/000ddc5eaf58e781c945d6321970ec40/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/000ddc5eaf58e781c945d6321970ec40/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 122bd1c6984abc9434e428156a96ada23d72631b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:34:01 -0600 Subject: [PATCH 368/489] change state of issue b62c30d419fb7727d2faaee20dfed1be, new -> backlog --- b62c30d419fb7727d2faaee20dfed1be/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 b62c30d419fb7727d2faaee20dfed1be/state diff --git a/b62c30d419fb7727d2faaee20dfed1be/state b/b62c30d419fb7727d2faaee20dfed1be/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/b62c30d419fb7727d2faaee20dfed1be/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 7e7c0f621ad71879fdc51566bfe0faf35728c2b0 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:34:45 -0600 Subject: [PATCH 369/489] edit description of issue 95a8190f4bbdcafbb4c72db81dfc2aa6 --- 95a8190f4bbdcafbb4c72db81dfc2aa6/description | 2 ++ 1 file changed, 2 insertions(+) diff --git a/95a8190f4bbdcafbb4c72db81dfc2aa6/description b/95a8190f4bbdcafbb4c72db81dfc2aa6/description index 58a4f48..758040b 100644 --- a/95a8190f4bbdcafbb4c72db81dfc2aa6/description +++ b/95a8190f4bbdcafbb4c72db81dfc2aa6/description @@ -1,3 +1,5 @@ lazy read of DB when running `list` don't load the entire object, instead only load the relevant metadata used to display in the list + +Similarly, when running e.g. `ent show ISSUE_ID`, don't load the other issues. From 6d22ed2aa4b161049c647aec233823c3fabdbd75 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:34:52 -0600 Subject: [PATCH 370/489] change state of issue 95a8190f4bbdcafbb4c72db81dfc2aa6, new -> backlog --- 95a8190f4bbdcafbb4c72db81dfc2aa6/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 95a8190f4bbdcafbb4c72db81dfc2aa6/state diff --git a/95a8190f4bbdcafbb4c72db81dfc2aa6/state b/95a8190f4bbdcafbb4c72db81dfc2aa6/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/95a8190f4bbdcafbb4c72db81dfc2aa6/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From fa4de6343a40f0cf1c8680ab0535cb0878167811 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:36:00 -0600 Subject: [PATCH 371/489] add comment 91846caf2947046990227faa1c8cf501 on issue cc7c13a6220f963436dcf29274dc45c5 --- .../comments/91846caf2947046990227faa1c8cf501/description | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 cc7c13a6220f963436dcf29274dc45c5/comments/91846caf2947046990227faa1c8cf501/description diff --git a/cc7c13a6220f963436dcf29274dc45c5/comments/91846caf2947046990227faa1c8cf501/description b/cc7c13a6220f963436dcf29274dc45c5/comments/91846caf2947046990227faa1c8cf501/description new file mode 100644 index 0000000..fba99d7 --- /dev/null +++ b/cc7c13a6220f963436dcf29274dc45c5/comments/91846caf2947046990227faa1c8cf501/description @@ -0,0 +1,2 @@ +This is partially addressed by +. From 9046b875335da3bcb5ae23bb40eb694caaea99b1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:36:08 -0600 Subject: [PATCH 372/489] change state of issue cc7c13a6220f963436dcf29274dc45c5, new -> backlog --- cc7c13a6220f963436dcf29274dc45c5/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 cc7c13a6220f963436dcf29274dc45c5/state diff --git a/cc7c13a6220f963436dcf29274dc45c5/state b/cc7c13a6220f963436dcf29274dc45c5/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/cc7c13a6220f963436dcf29274dc45c5/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 534e79f63ae4f4313fc84e28150a74319ccbd0bf Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:36:55 -0600 Subject: [PATCH 373/489] change state of issue 0f8b0c982bcfe7d5406bea58301014bc, new -> backlog --- 0f8b0c982bcfe7d5406bea58301014bc/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 0f8b0c982bcfe7d5406bea58301014bc/state diff --git a/0f8b0c982bcfe7d5406bea58301014bc/state b/0f8b0c982bcfe7d5406bea58301014bc/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/0f8b0c982bcfe7d5406bea58301014bc/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 2288b0deb20459c902b43865f233c5b81a702316 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:37:30 -0600 Subject: [PATCH 374/489] change state of issue 63bca383372acb1070f9b2416b2a84f6, new -> backlog --- 63bca383372acb1070f9b2416b2a84f6/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 63bca383372acb1070f9b2416b2a84f6/state diff --git a/63bca383372acb1070f9b2416b2a84f6/state b/63bca383372acb1070f9b2416b2a84f6/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/63bca383372acb1070f9b2416b2a84f6/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From b03cdac9b3a386265ac4bf1e3122f7621160b951 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:46:31 -0600 Subject: [PATCH 375/489] add comment 7ee4a01771a0cfa20d4c536f8f54573b on issue 54e366c80dfc6fc2dd5d52eb36023386 --- .../comments/7ee4a01771a0cfa20d4c536f8f54573b/description | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 54e366c80dfc6fc2dd5d52eb36023386/comments/7ee4a01771a0cfa20d4c536f8f54573b/description diff --git a/54e366c80dfc6fc2dd5d52eb36023386/comments/7ee4a01771a0cfa20d4c536f8f54573b/description b/54e366c80dfc6fc2dd5d52eb36023386/comments/7ee4a01771a0cfa20d4c536f8f54573b/description new file mode 100644 index 0000000..91e4373 --- /dev/null +++ b/54e366c80dfc6fc2dd5d52eb36023386/comments/7ee4a01771a0cfa20d4c536f8f54573b/description @@ -0,0 +1,8 @@ +We can determine something like an "mtime" for an Issue by running a +command like: + +`git log --pretty=format:%ai --max-count 1 entomologist-data -- ${ISSUE_ID}` + +Running it on `${ISSUE_ID}/comments` gives you the most recent time +there was a commit in the comments directory, indicating a new or +edited comment. From 4a9c6c6ef371f6f09dc57d0c466f02bce3e5dcf3 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:48:44 -0600 Subject: [PATCH 376/489] change state of issue 54e366c80dfc6fc2dd5d52eb36023386, new -> backlog --- 54e366c80dfc6fc2dd5d52eb36023386/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 54e366c80dfc6fc2dd5d52eb36023386/state diff --git a/54e366c80dfc6fc2dd5d52eb36023386/state b/54e366c80dfc6fc2dd5d52eb36023386/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/54e366c80dfc6fc2dd5d52eb36023386/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From efbf674c87e1880c3435bd929d46118716cfbd91 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:49:37 -0600 Subject: [PATCH 377/489] change state of issue 781360ade670846ed0ccdbfd19ffa8fd, new -> backlog --- 781360ade670846ed0ccdbfd19ffa8fd/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 781360ade670846ed0ccdbfd19ffa8fd/state diff --git a/781360ade670846ed0ccdbfd19ffa8fd/state b/781360ade670846ed0ccdbfd19ffa8fd/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/781360ade670846ed0ccdbfd19ffa8fd/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From a8218e6e46cc7820b2ba418b18de2a783efa6deb Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:50:14 -0600 Subject: [PATCH 378/489] issue 781360ade670846ed0ccdbfd19ffa8fd add tag db --- 781360ade670846ed0ccdbfd19ffa8fd/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 781360ade670846ed0ccdbfd19ffa8fd/tags diff --git a/781360ade670846ed0ccdbfd19ffa8fd/tags b/781360ade670846ed0ccdbfd19ffa8fd/tags new file mode 100644 index 0000000..65eef93 --- /dev/null +++ b/781360ade670846ed0ccdbfd19ffa8fd/tags @@ -0,0 +1 @@ +db From ad2f5b584dcf99284bff1666d371a4f7c0af91b0 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:51:36 -0600 Subject: [PATCH 379/489] change assignee of issue 7bf773d64437d6b92b7ffe6932531533, None -> seb --- 7bf773d64437d6b92b7ffe6932531533/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 7bf773d64437d6b92b7ffe6932531533/assignee diff --git a/7bf773d64437d6b92b7ffe6932531533/assignee b/7bf773d64437d6b92b7ffe6932531533/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/7bf773d64437d6b92b7ffe6932531533/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From 354ad1d1db232b42fd22711cd5d3016881db18b7 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:51:54 -0600 Subject: [PATCH 380/489] change state of issue 7bf773d64437d6b92b7ffe6932531533, new -> inprogress --- 7bf773d64437d6b92b7ffe6932531533/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 7bf773d64437d6b92b7ffe6932531533/state diff --git a/7bf773d64437d6b92b7ffe6932531533/state b/7bf773d64437d6b92b7ffe6932531533/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/7bf773d64437d6b92b7ffe6932531533/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 47e0e657434e874a5cefff316ebd4ee69910b781 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:52:37 -0600 Subject: [PATCH 381/489] change state of issue 1c771196c49c732932caacfa79ad56dc, new -> backlog --- 1c771196c49c732932caacfa79ad56dc/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 1c771196c49c732932caacfa79ad56dc/state diff --git a/1c771196c49c732932caacfa79ad56dc/state b/1c771196c49c732932caacfa79ad56dc/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/1c771196c49c732932caacfa79ad56dc/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From e81138b3da4a31807291f01cd7a0a682fcb1b74e Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:54:15 -0600 Subject: [PATCH 382/489] create new issue 31144ca83f6f3de1a9e3db651b70a8b4 --- 31144ca83f6f3de1a9e3db651b70a8b4/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 31144ca83f6f3de1a9e3db651b70a8b4/description diff --git a/31144ca83f6f3de1a9e3db651b70a8b4/description b/31144ca83f6f3de1a9e3db651b70a8b4/description new file mode 100644 index 0000000..d9f805b --- /dev/null +++ b/31144ca83f6f3de1a9e3db651b70a8b4/description @@ -0,0 +1 @@ +`ent assign` and maybe other commands needlessly read the database twice From 067a13c4df89f5884fb7188216dd42efa133691c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:54:33 -0600 Subject: [PATCH 383/489] change state of issue 31144ca83f6f3de1a9e3db651b70a8b4, new -> backlog --- 31144ca83f6f3de1a9e3db651b70a8b4/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 31144ca83f6f3de1a9e3db651b70a8b4/state diff --git a/31144ca83f6f3de1a9e3db651b70a8b4/state b/31144ca83f6f3de1a9e3db651b70a8b4/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/31144ca83f6f3de1a9e3db651b70a8b4/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 01e15f0c1a79dec062b65ea09c65ff924db0c338 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 20:55:14 -0600 Subject: [PATCH 384/489] change state of issue 478ac34c204be06b1da5b4f0b5a2532d, new -> backlog --- 478ac34c204be06b1da5b4f0b5a2532d/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 478ac34c204be06b1da5b4f0b5a2532d/state diff --git a/478ac34c204be06b1da5b4f0b5a2532d/state b/478ac34c204be06b1da5b4f0b5a2532d/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/478ac34c204be06b1da5b4f0b5a2532d/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 06d373615ccd2f5bd2dcb39a8acc54b8a6fd2c23 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 21:01:21 -0600 Subject: [PATCH 385/489] add comment d6a3fb287621b6d08bb9ab8cbad7cf53 on issue 4a9118e5e06956e0b0766ace15174297 --- .../d6a3fb287621b6d08bb9ab8cbad7cf53/description | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 4a9118e5e06956e0b0766ace15174297/comments/d6a3fb287621b6d08bb9ab8cbad7cf53/description diff --git a/4a9118e5e06956e0b0766ace15174297/comments/d6a3fb287621b6d08bb9ab8cbad7cf53/description b/4a9118e5e06956e0b0766ace15174297/comments/d6a3fb287621b6d08bb9ab8cbad7cf53/description new file mode 100644 index 0000000..aaf6056 --- /dev/null +++ b/4a9118e5e06956e0b0766ace15174297/comments/d6a3fb287621b6d08bb9ab8cbad7cf53/description @@ -0,0 +1,10 @@ +Of the three options you suggested i like "Hourglass Not Done" (⏳) +best, because it most clearly shows that we're waiting for something. + +Or how about Stop Sign, 🛑? + +Do you want `ent list` to show the number of issues that this issue is +blocked waiting on? Something like: + + 012345 This issues stands alone + abc123 ⏳4 This other issue depends on four things From 07b6ba224e3b9cfd0bc41b8410e972d9cb8a9e3f Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 21:02:20 -0600 Subject: [PATCH 386/489] change state of issue 4a9118e5e06956e0b0766ace15174297, new -> backlog --- 4a9118e5e06956e0b0766ace15174297/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 4a9118e5e06956e0b0766ace15174297/state diff --git a/4a9118e5e06956e0b0766ace15174297/state b/4a9118e5e06956e0b0766ace15174297/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/4a9118e5e06956e0b0766ace15174297/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 8af9c71ef6953471db44acdd56bd707bb85ff4b7 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 21:10:14 -0600 Subject: [PATCH 387/489] `ent done-time ISSUE TIME`: report parse error instead of panicking --- src/bin/ent/main.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 1698954..651e0fc 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -499,9 +499,13 @@ fn handle_command( let Some(issue) = issues.get_mut_issue(issue_id) else { return Err(anyhow::anyhow!("issue {} not found", issue_id)); }; - let done_time = chrono::DateTime::parse_from_rfc3339(done_time) - .unwrap() - .with_timezone(&chrono::Local); + 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 { From 0209decdccc42371bb92dd8ee3563492e6874ba9 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 21:13:32 -0600 Subject: [PATCH 388/489] add comment 55507ab98176d4d7cf12f0c794246aaf on issue a97c817024233be0e34536dfb1323070 --- .../comments/55507ab98176d4d7cf12f0c794246aaf/description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 a97c817024233be0e34536dfb1323070/comments/55507ab98176d4d7cf12f0c794246aaf/description diff --git a/a97c817024233be0e34536dfb1323070/comments/55507ab98176d4d7cf12f0c794246aaf/description b/a97c817024233be0e34536dfb1323070/comments/55507ab98176d4d7cf12f0c794246aaf/description new file mode 100644 index 0000000..f0f9795 --- /dev/null +++ b/a97c817024233be0e34536dfb1323070/comments/55507ab98176d4d7cf12f0c794246aaf/description @@ -0,0 +1,4 @@ +This was fixed in +. + +I will fudge the done-time of this issue to match the merge of that PR. From ab53b61358927bf67fb8276db49b2f60ba61b0b5 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 21:14:27 -0600 Subject: [PATCH 389/489] set done-time of issue a97c817024233be0e34536dfb1323070 to 2025-07-17 12:02:42 -06:00 --- a97c817024233be0e34536dfb1323070/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 a97c817024233be0e34536dfb1323070/done_time diff --git a/a97c817024233be0e34536dfb1323070/done_time b/a97c817024233be0e34536dfb1323070/done_time new file mode 100644 index 0000000..df0d40a --- /dev/null +++ b/a97c817024233be0e34536dfb1323070/done_time @@ -0,0 +1 @@ +2025-07-17T12:02:42-06:00 \ No newline at end of file From 6d4003cc1df1bf32912efb0d90bc87748121fb04 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 21:14:43 -0600 Subject: [PATCH 390/489] change state of issue a97c817024233be0e34536dfb1323070, backlog -> done --- a97c817024233be0e34536dfb1323070/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/a97c817024233be0e34536dfb1323070/state b/a97c817024233be0e34536dfb1323070/state index b6fe829..348ebd9 100644 --- a/a97c817024233be0e34536dfb1323070/state +++ b/a97c817024233be0e34536dfb1323070/state @@ -1 +1 @@ -backlog \ No newline at end of file +done \ No newline at end of file From ff0eadd551d032d8c79dba80abca59ab1b4865d1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 21:14:43 -0600 Subject: [PATCH 391/489] set done-time of issue a97c817024233be0e34536dfb1323070 to 2025-07-19 21:14:43.990343613 -06:00 --- a97c817024233be0e34536dfb1323070/done_time | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/a97c817024233be0e34536dfb1323070/done_time b/a97c817024233be0e34536dfb1323070/done_time index df0d40a..e4203a5 100644 --- a/a97c817024233be0e34536dfb1323070/done_time +++ b/a97c817024233be0e34536dfb1323070/done_time @@ -1 +1 @@ -2025-07-17T12:02:42-06:00 \ No newline at end of file +2025-07-19T21:14:43.990343613-06:00 \ No newline at end of file From 4f9282c8547986d8b2095023676c078491333131 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 21:15:07 -0600 Subject: [PATCH 392/489] set done-time of issue a97c817024233be0e34536dfb1323070 to 2025-07-17 12:02:42 -06:00 --- a97c817024233be0e34536dfb1323070/done_time | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/a97c817024233be0e34536dfb1323070/done_time b/a97c817024233be0e34536dfb1323070/done_time index e4203a5..df0d40a 100644 --- a/a97c817024233be0e34536dfb1323070/done_time +++ b/a97c817024233be0e34536dfb1323070/done_time @@ -1 +1 @@ -2025-07-19T21:14:43.990343613-06:00 \ No newline at end of file +2025-07-17T12:02:42-06:00 \ No newline at end of file From ed0d6f4d14d42e3a3c515bcd4b69d6abde5b25f9 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 21:16:52 -0600 Subject: [PATCH 393/489] change assignee of issue 8ef792a2092e5ba84da3cb4efc3c88c6, None -> seb --- 8ef792a2092e5ba84da3cb4efc3c88c6/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 8ef792a2092e5ba84da3cb4efc3c88c6/assignee diff --git a/8ef792a2092e5ba84da3cb4efc3c88c6/assignee b/8ef792a2092e5ba84da3cb4efc3c88c6/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/8ef792a2092e5ba84da3cb4efc3c88c6/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From 9e451ea6be1a6bad67c8c20770d9c812dc571a26 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 19 Jul 2025 21:17:01 -0600 Subject: [PATCH 394/489] change state of issue 8ef792a2092e5ba84da3cb4efc3c88c6, backlog -> inprogress --- 8ef792a2092e5ba84da3cb4efc3c88c6/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/8ef792a2092e5ba84da3cb4efc3c88c6/state b/8ef792a2092e5ba84da3cb4efc3c88c6/state index b6fe829..505c028 100644 --- a/8ef792a2092e5ba84da3cb4efc3c88c6/state +++ b/8ef792a2092e5ba84da3cb4efc3c88c6/state @@ -1 +1 @@ -backlog \ No newline at end of file +inprogress \ No newline at end of file From 0d9a893087b50e66a05def0d4f4f6d9f9054975e Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 20 Jul 2025 00:01:19 -0600 Subject: [PATCH 395/489] `ent show`: simplify logic This simplifies the code flow and gets rid of two levels of indentation. --- src/bin/ent/main.rs | 54 +++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 1698954..c9327e1 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -285,35 +285,31 @@ fn handle_command( Commands::Show { issue_id } => { let issues = entomologist::database::read_issues_database(issues_database_source)?; - match issues.get_issue(issue_id) { - Some(issue) => { - println!("issue {}", issue_id); - println!("author: {}", issue.author); - println!("creation_time: {}", issue.creation_time); - if let Some(done_time) = &issue.done_time { - println!("done_time: {}", done_time); - } - println!("state: {:?}", issue.state); - if let Some(dependencies) = &issue.dependencies { - println!("dependencies: {:?}", dependencies); - } - if let Some(assignee) = &issue.assignee { - println!("assignee: {}", assignee); - } - println!(""); - println!("{}", issue.description); - for comment in &issue.comments { - println!(""); - println!("comment: {}", comment.uuid); - println!("author: {}", comment.author); - println!("creation_time: {}", comment.creation_time); - println!(""); - println!("{}", comment.description); - } - } - None => { - return Err(anyhow::anyhow!("issue {} not found", issue_id)); - } + let Some(issue) = issues.get_issue(issue_id) else { + return Err(anyhow::anyhow!("issue {} not found", issue_id)); + }; + println!("issue {}", issue_id); + println!("author: {}", issue.author); + println!("creation_time: {}", issue.creation_time); + if let Some(done_time) = &issue.done_time { + println!("done_time: {}", done_time); + } + println!("state: {:?}", issue.state); + if let Some(dependencies) = &issue.dependencies { + println!("dependencies: {:?}", dependencies); + } + if let Some(assignee) = &issue.assignee { + println!("assignee: {}", assignee); + } + println!(""); + println!("{}", issue.description); + for comment in &issue.comments { + println!(""); + println!("comment: {}", comment.uuid); + println!("author: {}", comment.author); + println!("creation_time: {}", comment.creation_time); + println!(""); + println!("{}", comment.description); } } From c9dbec730cf18c5175a38c84b78443c204a0206f Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 20 Jul 2025 00:04:11 -0600 Subject: [PATCH 396/489] `ent show`: show tags, if any --- src/bin/ent/main.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index c9327e1..13550db 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -290,6 +290,15 @@ fn handle_command( }; println!("issue {}", issue_id); println!("author: {}", issue.author); + if issue.tags.len() > 0 { + print!("tags: "); + let mut separator = ""; + for tag in &issue.tags { + print!("{}{}", separator, tag); + separator = ", "; + } + println!(""); + } println!("creation_time: {}", issue.creation_time); if let Some(done_time) = &issue.done_time { println!("done_time: {}", done_time); From f83fc5cba48eafe6efc281289159ed3edff6c0c0 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 11:53:07 -0600 Subject: [PATCH 397/489] add comment b132544d85794eaf48e25955a431ab5a on issue 4a9118e5e06956e0b0766ace15174297 --- .../comments/b132544d85794eaf48e25955a431ab5a/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 4a9118e5e06956e0b0766ace15174297/comments/b132544d85794eaf48e25955a431ab5a/description diff --git a/4a9118e5e06956e0b0766ace15174297/comments/b132544d85794eaf48e25955a431ab5a/description b/4a9118e5e06956e0b0766ace15174297/comments/b132544d85794eaf48e25955a431ab5a/description new file mode 100644 index 0000000..e823c1a --- /dev/null +++ b/4a9118e5e06956e0b0766ace15174297/comments/b132544d85794eaf48e25955a431ab5a/description @@ -0,0 +1,3 @@ +stop sign is an interesting thought, does definitely represent that the issue is blocked... although it feels like a very visually heavy emoji to use, and if we use a color to represent issue state later on Stop Sign is similar to Red Circle and might get visually confusing. + +and yeah, that's exactly what i'm envisioning - i'd like to be able to see at a glance that an issue has dependencies. it might be nice to see the state of those dependencies? but that starts to feel off in the weeds. for now just seeing the count would be nice i think. From 8a92bf2637e26536bb7fef7aa1920ed36b7ffc98 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 11:55:29 -0600 Subject: [PATCH 398/489] fix test directory to match updated dependency representation --- test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies | 2 -- .../dependencies/3fa5bfd93317ad25772680071d5ac3259cd2384f | 0 .../dependencies/dd79c8cfb8beeacd0460429944b4ecbe95a31561 | 0 3 files changed, 2 deletions(-) delete mode 100644 test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies create mode 100644 test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies/3fa5bfd93317ad25772680071d5ac3259cd2384f create mode 100644 test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies/dd79c8cfb8beeacd0460429944b4ecbe95a31561 diff --git a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies deleted file mode 100644 index 71e4ee3..0000000 --- a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies +++ /dev/null @@ -1,2 +0,0 @@ -3fa5bfd93317ad25772680071d5ac3259cd2384f -dd79c8cfb8beeacd0460429944b4ecbe95a31561 \ No newline at end of file diff --git a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies/3fa5bfd93317ad25772680071d5ac3259cd2384f b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies/3fa5bfd93317ad25772680071d5ac3259cd2384f new file mode 100644 index 0000000..e69de29 diff --git a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies/dd79c8cfb8beeacd0460429944b4ecbe95a31561 b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies/dd79c8cfb8beeacd0460429944b4ecbe95a31561 new file mode 100644 index 0000000..e69de29 From 8a2590021124968d7e3a9461205a485d7023aebe Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 11:58:51 -0600 Subject: [PATCH 399/489] add comment 4910a6080a1dee6586ad57827f8774fb on issue 7e2a3a59fb6b77403ff1035255367607 --- .../comments/4910a6080a1dee6586ad57827f8774fb/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 7e2a3a59fb6b77403ff1035255367607/comments/4910a6080a1dee6586ad57827f8774fb/description diff --git a/7e2a3a59fb6b77403ff1035255367607/comments/4910a6080a1dee6586ad57827f8774fb/description b/7e2a3a59fb6b77403ff1035255367607/comments/4910a6080a1dee6586ad57827f8774fb/description new file mode 100644 index 0000000..4e69377 --- /dev/null +++ b/7e2a3a59fb6b77403ff1035255367607/comments/4910a6080a1dee6586ad57827f8774fb/description @@ -0,0 +1,3 @@ +pushed new commit to the MR to fix the test that broke with the new dependencies layout. + +IRL discussion landed on this being OK because the CLI was never released with the old dependencies layout, so unless someone has manually created a dependencies file, this should not have an impact. From 9b8c077653861b9507e3b5dd75272879ceac318c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 20 Jul 2025 11:58:47 -0600 Subject: [PATCH 400/489] remove Todo file, we have entomologist-data now --- Todo.md | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 Todo.md diff --git a/Todo.md b/Todo.md deleted file mode 100644 index 9594c71..0000000 --- a/Todo.md +++ /dev/null @@ -1,19 +0,0 @@ -# To do - -* migrate this todo list into entomologist - -* implement user control over state transitions - -* implement `ent comment ${ISSUE} [-m ${MESSAGE}]` - - each issue dir has a `comments` subdir - - each comment is identified by a sha1-style uid - - each comment is a file or directory under the `${ISSUE}/comments` - - comments are ordered by ctime? - -* implement `ent edit ${COMMENT}` - -* implement `ent attach ${ISSUE} ${FILE}` - - each issue has its own independent namespace for attached files - - issue description & comments can reference attached files via standard md links - -* write a manpage From 5b3e5a654056ec0ef2a5b5b57b8cd5bea76e586a Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 20 Jul 2025 12:13:37 -0600 Subject: [PATCH 401/489] add comment 9c4dc8aa38ba0a9a6f3fbc014b05f7ba on issue dd20d3ddc86ee802fe7b15e2c91dc160 --- .../description | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 dd20d3ddc86ee802fe7b15e2c91dc160/comments/9c4dc8aa38ba0a9a6f3fbc014b05f7ba/description diff --git a/dd20d3ddc86ee802fe7b15e2c91dc160/comments/9c4dc8aa38ba0a9a6f3fbc014b05f7ba/description b/dd20d3ddc86ee802fe7b15e2c91dc160/comments/9c4dc8aa38ba0a9a6f3fbc014b05f7ba/description new file mode 100644 index 0000000..9918ec7 --- /dev/null +++ b/dd20d3ddc86ee802fe7b15e2c91dc160/comments/9c4dc8aa38ba0a9a6f3fbc014b05f7ba/description @@ -0,0 +1,33 @@ +There are `entomologist-data` branches "in the wild" that have issues +with tags files, so we need to migrate those somehow. + +We discussed a couple of ways to do this: + +1. Have the ent library do it automatically and transparently when it + comes across an old-style tags file. In `Issue::new_from_dir()` we'd + detect the old format, parse it, then immediately delete the file, + write the new format directory, and commit the result. + + This is good: + - It's easy for the human + + This is bad: + - entomologist will need to carry along this crufty code until we + think there are no more old-style repos in existence + + +2. Make a stand-alone tool (like the `set-done-time` tool in + . + The ent binary does *not* know how to read the old tags file, and will + error out if it comes across an issue with such a file. The human + would then manually run the tags-representation updater tool. + This tool would make the git worktree, walk the issues, find the + old-style tags, rewrite them in the new format, and commit the result. + + This is good: + - Keeps the core entomologist code clean and "modern". + + This is bad: + - This tool does manual/direct manipulation of the on-disk format, + which means it duplicates a lot of what the entomologist library + does. From 6ef9c62d152a5f87f5e9a11bdf1c8b2b83354417 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 21:09:07 -0600 Subject: [PATCH 402/489] add comment 6681d522c81311810f09248751bd47cc on issue 5f59a02e5320b62a68ad704da17e0ff4 --- .../comments/6681d522c81311810f09248751bd47cc/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 5f59a02e5320b62a68ad704da17e0ff4/comments/6681d522c81311810f09248751bd47cc/description diff --git a/5f59a02e5320b62a68ad704da17e0ff4/comments/6681d522c81311810f09248751bd47cc/description b/5f59a02e5320b62a68ad704da17e0ff4/comments/6681d522c81311810f09248751bd47cc/description new file mode 100644 index 0000000..24e19d2 --- /dev/null +++ b/5f59a02e5320b62a68ad704da17e0ff4/comments/6681d522c81311810f09248751bd47cc/description @@ -0,0 +1,3 @@ +yeah exactly that. just a convenience thing to be able to (from any directory) run `ent update`. + +i could just make it an alias as well... not sure which is more idiomatic From 423182b10564c0ec9fc09fa062e4be4898efc3f5 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 21:13:20 -0600 Subject: [PATCH 403/489] create new issue 01b9499f6a927ca6326b91037bfe0821 --- 01b9499f6a927ca6326b91037bfe0821/description | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 01b9499f6a927ca6326b91037bfe0821/description diff --git a/01b9499f6a927ca6326b91037bfe0821/description b/01b9499f6a927ca6326b91037bfe0821/description new file mode 100644 index 0000000..4dba385 --- /dev/null +++ b/01b9499f6a927ca6326b91037bfe0821/description @@ -0,0 +1,11 @@ +collections + +introduce a new data structure - a "collection". + +a collection is a grouping of: +1. ent repositories +2. other collections + +a collection is defined by some user-producible, machine readable file (maybe a TOML or something similar) and points to: +1. locations of other ent repositories (local or remote) +2. locations of other collection files (local or remote) From 72b96658421617e47f84dc56a92d56611ff88041 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 21:13:35 -0600 Subject: [PATCH 404/489] issue 01b9499f6a927ca6326b91037bfe0821 add tag feature --- 01b9499f6a927ca6326b91037bfe0821/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 01b9499f6a927ca6326b91037bfe0821/tags diff --git a/01b9499f6a927ca6326b91037bfe0821/tags b/01b9499f6a927ca6326b91037bfe0821/tags new file mode 100644 index 0000000..a7453f0 --- /dev/null +++ b/01b9499f6a927ca6326b91037bfe0821/tags @@ -0,0 +1 @@ +feature From 924e887fccbedd5caac83841281c679fb2e96eef Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 21:14:12 -0600 Subject: [PATCH 405/489] add comment 8df399ccd57f285a2edb3b058560c1a4 on issue 87fa3146b90db61c4ea0de182798a0e5 --- .../comments/8df399ccd57f285a2edb3b058560c1a4/description | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 87fa3146b90db61c4ea0de182798a0e5/comments/8df399ccd57f285a2edb3b058560c1a4/description diff --git a/87fa3146b90db61c4ea0de182798a0e5/comments/8df399ccd57f285a2edb3b058560c1a4/description b/87fa3146b90db61c4ea0de182798a0e5/comments/8df399ccd57f285a2edb3b058560c1a4/description new file mode 100644 index 0000000..edc2d3b --- /dev/null +++ b/87fa3146b90db61c4ea0de182798a0e5/comments/8df399ccd57f285a2edb3b058560c1a4/description @@ -0,0 +1,5 @@ +noticing the following two commands are very slow: +* `ent comment` +* `ent tag` + +`ent tag` in particular is _very_ slow From 2ae2b46a82286dbb8795574e9a1bc73b774c8107 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 21:15:00 -0600 Subject: [PATCH 406/489] issue 87fa3146b90db61c4ea0de182798a0e5 add tag issue --- 87fa3146b90db61c4ea0de182798a0e5/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 87fa3146b90db61c4ea0de182798a0e5/tags diff --git a/87fa3146b90db61c4ea0de182798a0e5/tags b/87fa3146b90db61c4ea0de182798a0e5/tags new file mode 100644 index 0000000..7877337 --- /dev/null +++ b/87fa3146b90db61c4ea0de182798a0e5/tags @@ -0,0 +1 @@ +issue From 24ad81b43da473d6ac0e48e44eb0ad9fed31cf32 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 21:15:35 -0600 Subject: [PATCH 407/489] issue 478ac34c204be06b1da5b4f0b5a2532d add tag debug --- 478ac34c204be06b1da5b4f0b5a2532d/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 478ac34c204be06b1da5b4f0b5a2532d/tags diff --git a/478ac34c204be06b1da5b4f0b5a2532d/tags b/478ac34c204be06b1da5b4f0b5a2532d/tags new file mode 100644 index 0000000..d287cd3 --- /dev/null +++ b/478ac34c204be06b1da5b4f0b5a2532d/tags @@ -0,0 +1 @@ +debug From 308d87dc30cb8d08b4c9f914e72278fb8caaf662 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 21:16:47 -0600 Subject: [PATCH 408/489] issue 1f85dfac686d5ea2417b2b07f7e1ff01 add tag feature --- 1f85dfac686d5ea2417b2b07f7e1ff01/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 1f85dfac686d5ea2417b2b07f7e1ff01/tags diff --git a/1f85dfac686d5ea2417b2b07f7e1ff01/tags b/1f85dfac686d5ea2417b2b07f7e1ff01/tags new file mode 100644 index 0000000..a7453f0 --- /dev/null +++ b/1f85dfac686d5ea2417b2b07f7e1ff01/tags @@ -0,0 +1 @@ +feature From 2f983f47a956c18802e3e351dd8a17821cdba455 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 21:16:55 -0600 Subject: [PATCH 409/489] change assignee of issue 1f85dfac686d5ea2417b2b07f7e1ff01, None -> sigil-03 --- 1f85dfac686d5ea2417b2b07f7e1ff01/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 1f85dfac686d5ea2417b2b07f7e1ff01/assignee diff --git a/1f85dfac686d5ea2417b2b07f7e1ff01/assignee b/1f85dfac686d5ea2417b2b07f7e1ff01/assignee new file mode 100644 index 0000000..284bb3b --- /dev/null +++ b/1f85dfac686d5ea2417b2b07f7e1ff01/assignee @@ -0,0 +1 @@ +sigil-03 \ No newline at end of file From f6f513a38cb6bc69f8782e8310b4ca58736461bc Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 21:17:18 -0600 Subject: [PATCH 410/489] change state of issue 5e1a860b3ab12ee297492d70d68711d8, inprogress -> backlog --- 5e1a860b3ab12ee297492d70d68711d8/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5e1a860b3ab12ee297492d70d68711d8/state b/5e1a860b3ab12ee297492d70d68711d8/state index 505c028..b6fe829 100644 --- a/5e1a860b3ab12ee297492d70d68711d8/state +++ b/5e1a860b3ab12ee297492d70d68711d8/state @@ -1 +1 @@ -inprogress \ No newline at end of file +backlog \ No newline at end of file From 10e2cab48285f32cb025774d210955692ac046f9 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 21:17:24 -0600 Subject: [PATCH 411/489] change state of issue 88d5111fd6e59802d0b839ff1fd6bf71, inprogress -> backlog --- 88d5111fd6e59802d0b839ff1fd6bf71/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/88d5111fd6e59802d0b839ff1fd6bf71/state b/88d5111fd6e59802d0b839ff1fd6bf71/state index 505c028..b6fe829 100644 --- a/88d5111fd6e59802d0b839ff1fd6bf71/state +++ b/88d5111fd6e59802d0b839ff1fd6bf71/state @@ -1 +1 @@ -inprogress \ No newline at end of file +backlog \ No newline at end of file From bea9307bed9ae0c591165ec9e85e311ae5a3032d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 21:17:29 -0600 Subject: [PATCH 412/489] change state of issue bd00a62f9d7c77fd8dd0da5d20aa803d, inprogress -> backlog --- bd00a62f9d7c77fd8dd0da5d20aa803d/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bd00a62f9d7c77fd8dd0da5d20aa803d/state b/bd00a62f9d7c77fd8dd0da5d20aa803d/state index 505c028..b6fe829 100644 --- a/bd00a62f9d7c77fd8dd0da5d20aa803d/state +++ b/bd00a62f9d7c77fd8dd0da5d20aa803d/state @@ -1 +1 @@ -inprogress \ No newline at end of file +backlog \ No newline at end of file From e611ede5e0e09303bdd31542ed0cfc6ca9f29b55 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 21:17:35 -0600 Subject: [PATCH 413/489] change state of issue bdf44bd55fdecdfc1badcb7123d2a606, inprogress -> backlog --- bdf44bd55fdecdfc1badcb7123d2a606/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdf44bd55fdecdfc1badcb7123d2a606/state b/bdf44bd55fdecdfc1badcb7123d2a606/state index 505c028..b6fe829 100644 --- a/bdf44bd55fdecdfc1badcb7123d2a606/state +++ b/bdf44bd55fdecdfc1badcb7123d2a606/state @@ -1 +1 @@ -inprogress \ No newline at end of file +backlog \ No newline at end of file From 6161d1495e1709df8e7fc31ef4e6cded4a0dab20 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 20 Jul 2025 21:18:05 -0600 Subject: [PATCH 414/489] change state of issue 1f85dfac686d5ea2417b2b07f7e1ff01, backlog -> inprogress --- 1f85dfac686d5ea2417b2b07f7e1ff01/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/1f85dfac686d5ea2417b2b07f7e1ff01/state b/1f85dfac686d5ea2417b2b07f7e1ff01/state index b6fe829..505c028 100644 --- a/1f85dfac686d5ea2417b2b07f7e1ff01/state +++ b/1f85dfac686d5ea2417b2b07f7e1ff01/state @@ -1 +1 @@ -backlog \ No newline at end of file +inprogress \ No newline at end of file From f60dd18c0afd7132a459a18cccbab8f6a32ea2de Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 20 Jul 2025 22:22:41 -0600 Subject: [PATCH 415/489] sort dependencies alphabetically after reading them This is mostly to make the tests reliable. Without this the dependencies are inserted into the vector in directory order, which in my checkout of the repo did not match the alphabetical order of the dependencies in the test. --- src/issue.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/issue.rs b/src/issue.rs index 5022ace..aafb4e2 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -212,6 +212,9 @@ impl Issue { } } } + if let Some(deps) = &mut dependencies { + deps.sort(); + } Ok(dependencies) } From 7d479da4a64e0d974122f7fbe92b14a9860b7820 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sun, 20 Jul 2025 22:59:20 -0600 Subject: [PATCH 416/489] add comment 0efebcbcf60417a54382a408b99dadf5 on issue dd20d3ddc86ee802fe7b15e2c91dc160 --- .../description | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 dd20d3ddc86ee802fe7b15e2c91dc160/comments/0efebcbcf60417a54382a408b99dadf5/description diff --git a/dd20d3ddc86ee802fe7b15e2c91dc160/comments/0efebcbcf60417a54382a408b99dadf5/description b/dd20d3ddc86ee802fe7b15e2c91dc160/comments/0efebcbcf60417a54382a408b99dadf5/description new file mode 100644 index 0000000..74afbd6 --- /dev/null +++ b/dd20d3ddc86ee802fe7b15e2c91dc160/comments/0efebcbcf60417a54382a408b99dadf5/description @@ -0,0 +1,21 @@ +There's a fly in the ointment: we want to store tags as files in a +directory, but file names are not allowed to contain the character `/`, +and some of our current tags use `/` in the tag name. + +We could allow subdirectories of the `tags` directory, but it feels +like that breaks the budding database abstraction of "a directory is a +key/value store, filenames are the keys, file contents are the values". + +We could forbid `/` in tags and replace them with `.` or `_` or something, +but that's a bit sad because i like `/` as the "hierarchy separator". + +Wikipedia claims, and experiment confirms: +> The big solidus ⧸ (Unicode code point U+29F8) is permitted in Unix +> and Windows filenames. + + + +But that feels like madness. + +I think the least worst option is to replace `/` with `.` in tags. +Opinions? From 6fe4d15412cf7554d92242dc51965bac2ab69499 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Mon, 21 Jul 2025 12:58:30 -0600 Subject: [PATCH 417/489] create new issue 9f0d77fa44e265a4e316f94be42554a5 --- 9f0d77fa44e265a4e316f94be42554a5/description | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 9f0d77fa44e265a4e316f94be42554a5/description diff --git a/9f0d77fa44e265a4e316f94be42554a5/description b/9f0d77fa44e265a4e316f94be42554a5/description new file mode 100644 index 0000000..4e1687d --- /dev/null +++ b/9f0d77fa44e265a4e316f94be42554a5/description @@ -0,0 +1,11 @@ +`ent list` should take a git-like --pretty=format: + +The immediate use is: +* list all issues in state=inprogress, but only tell me the assignees of each + +I currently do this with a slow and awkward shell script: +``` +for ISSUE_ID in $(ent list state=inprogress | grep ' ' | cut -f 1 -d ' '); do + ent show "${ISSUE_ID}" | grep -i assign +done +``` From 983ec5c475d93ab808643c5dc0e6fc50f682af78 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 10:03:25 -0600 Subject: [PATCH 418/489] change state of issue 7bf773d64437d6b92b7ffe6932531533, inprogress -> done --- 7bf773d64437d6b92b7ffe6932531533/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7bf773d64437d6b92b7ffe6932531533/state b/7bf773d64437d6b92b7ffe6932531533/state index 505c028..348ebd9 100644 --- a/7bf773d64437d6b92b7ffe6932531533/state +++ b/7bf773d64437d6b92b7ffe6932531533/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 4db4635abcc306ce6d87d3d7727101ba667eac1b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 10:03:25 -0600 Subject: [PATCH 419/489] set done-time of issue 7bf773d64437d6b92b7ffe6932531533 to 2025-07-22 10:03:25.567072982 -06:00 --- 7bf773d64437d6b92b7ffe6932531533/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 7bf773d64437d6b92b7ffe6932531533/done_time diff --git a/7bf773d64437d6b92b7ffe6932531533/done_time b/7bf773d64437d6b92b7ffe6932531533/done_time new file mode 100644 index 0000000..d467d60 --- /dev/null +++ b/7bf773d64437d6b92b7ffe6932531533/done_time @@ -0,0 +1 @@ +2025-07-22T10:03:25.567072982-06:00 \ No newline at end of file From 452671d272e049263030cacc9ea1afba56beb926 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 10:28:29 -0600 Subject: [PATCH 420/489] 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 421/489] 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 422/489] 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 423/489] 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 0494f3d2e35c82bca2baa1419756602c3a6d2179 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 11:04:21 -0600 Subject: [PATCH 424/489] add comment 9a99ad629c31320056d85b96113cf5e2 on issue 87fa3146b90db61c4ea0de182798a0e5 --- .../description | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 87fa3146b90db61c4ea0de182798a0e5/comments/9a99ad629c31320056d85b96113cf5e2/description diff --git a/87fa3146b90db61c4ea0de182798a0e5/comments/9a99ad629c31320056d85b96113cf5e2/description b/87fa3146b90db61c4ea0de182798a0e5/comments/9a99ad629c31320056d85b96113cf5e2/description new file mode 100644 index 0000000..8c01121 --- /dev/null +++ b/87fa3146b90db61c4ea0de182798a0e5/comments/9a99ad629c31320056d85b96113cf5e2/description @@ -0,0 +1,18 @@ +I ran ent under perf with this command: +``` +$ perf record -g -F 999 ent tag 7e2a3a59fb6b77403ff1035255367607 +``` + +This produced `perf.data`, which i converted to the format supported by the firefox profile tool: +``` +$ perf script -F +pid > /tmp/test.perf +``` + +I uploaded this to the firefox visualizer at . + +If i'm interpreting this correctly i think it's showing that we spend +most of our time running `git log` in different ways to determine the +author and creation-time of the issues. + +If so, lazy load (95a8190f4bbdcafbb4c72db81dfc2aa6) should speed up ent +a lot. From b0b14187c193503eac47af54422470992feea199 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 12:45:29 -0600 Subject: [PATCH 425/489] edit description of issue 781360ade670846ed0ccdbfd19ffa8fd --- 781360ade670846ed0ccdbfd19ffa8fd/description | 117 +++++++++++++++++-- 1 file changed, 108 insertions(+), 9 deletions(-) diff --git a/781360ade670846ed0ccdbfd19ffa8fd/description b/781360ade670846ed0ccdbfd19ffa8fd/description index d74158f..30b9ed7 100644 --- a/781360ade670846ed0ccdbfd19ffa8fd/description +++ b/781360ade670846ed0ccdbfd19ffa8fd/description @@ -1,15 +1,114 @@ -add a layer to the architecture with a DB api between the FS and ent? +add a database layer to the architecture between the FS and ent? -This issue grew out of the discussion in issue -08f0d7ee7842c439382816d21ec1dea2. +This issue is to consider adding a database abstraction between the +entomologist library and the git-backed filesystem. -Currently the entomologist crate code directly reads and writes files -and calls `git commit` when it wants. The directory is managed by the -application, generally as an ephemeral worktree containing a (possibly -detached) checkout of the `entomologist-data` branch. +Currently the entomologist library code directly reads and writes files +and directories, and calls `git commit` when it wants. The directory +is generally an ephemeral worktree containing a (possibly detached) +checkout of the `entomologist-data` branch. We may be able to simplify the ent internals (and the application) by adding a "database" API between ent and the filesystem. -There is some preliminary design brainstorming in the issue mentioned -above. +(This issue grew out of the discussion in issue +08f0d7ee7842c439382816d21ec1dea2. I've distilled the discussion in that +issue here.) + +for the filesystem DB, i think it might make sense to have a hashmap that stores everything as a key-value pair, where each key is a file, and each value is the contents of that file. + +once we go up the stack, i think it makes sense to have things in concrete structs, since that's easier to reason about. + +this also frees up the filesystem DB to get used for other things potentially, not just issues. + +So we'd have a system like this: +``` +git-backed filesystem +^ +| +v +db layer: key/value pair +^ +| +v +entomologist library layer: concrete structs (like `Issue` etc) +^ +| +v +presentation layer: (CLI / TUI / etc.) +``` + + +# Entomologist library API (presented up to the applicationA) + +Very similar to current entomologist library API. + +* Open the database at this DatabaseSource (filesystem path or git branch, + read-only or read-write, returns an opaque "entdb"(?) handle) + +* List issues in entdb + +* Add/edit issue + +* Get/set issue state/tags/assignee/done-time/etc + +* Add/edit comment on issue + + +# Database API (presented up to entomologist library) + +* Open the database at this DB Source (filesystem path or git branch, + read-only or read-write, returns an opaque "db" handle) + +* Read a db object into a key/value store. + + - Keys are filenames. Values are the file contents of that file, + or a database if the filename refers to a directory. + + - The read is by default *not* recursive for performance reasons; + the application may choose to read a "sub-database" referred to + by a key in the "parent database" if it wants, when it wants. + + - The application receives a k/v store and is responsible for + unpacking/interpreting/parsing that into some app-level struct + that is meaningful to the application. + +* Write a key-value store to a db. + + - Commits by default (the application supplies the commit message), + though maybe we want a way to stage multiple changes and commit + at the end? + + - The application transcodes its internal struct into a generic k/v + store for the db library. + +On write operations, the git commit message should be meaningful to +the application. Maybe that can be done generically by the db library, +or maybe the application needs to supply the commit message. + + +# Design + +A filesystem stores two kinds of things: directories and files. +A directory contains files, and other directories. + +Git stores two kinds of things: trees and blobs. Trees contain blobs, +and other trees. + +This DB tracks two kinds of things: databases and key/value objects. +Databases store key/value objects, and other databases. + +Some things we'd want from this DB layer: + +* Filesystem objects correspond to structs, like how we have each struct + Issue in its own issue directory. + +* Structs are nested, like how struct Issue contains struct Comment + +* Some fields are simple types (`author` is String), some are + less simple (`timestamp` is chrono::DateTime), some are custom + (`state` is enum State), and some are complicated (`dependencies` + is Option>, `comments` is Vec) + +* Filesystem objects are optimized for getting tracked by git - minimize + merge conflicts. From c15736259c445311120bbb52e24175e745f34d16 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 13:04:54 -0600 Subject: [PATCH 426/489] 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 ea871643af8c76708757dc2dab00c99bd31fab14 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 13:10:23 -0600 Subject: [PATCH 427/489] change state of issue 87fa3146b90db61c4ea0de182798a0e5, new -> new --- 87fa3146b90db61c4ea0de182798a0e5/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 87fa3146b90db61c4ea0de182798a0e5/state diff --git a/87fa3146b90db61c4ea0de182798a0e5/state b/87fa3146b90db61c4ea0de182798a0e5/state new file mode 100644 index 0000000..3e5126c --- /dev/null +++ b/87fa3146b90db61c4ea0de182798a0e5/state @@ -0,0 +1 @@ +new \ No newline at end of file From 6ddd800ab119814274beb462326eee356bc55299 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 13:10:27 -0600 Subject: [PATCH 428/489] change state of issue 87fa3146b90db61c4ea0de182798a0e5, new -> backlog --- 87fa3146b90db61c4ea0de182798a0e5/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/87fa3146b90db61c4ea0de182798a0e5/state b/87fa3146b90db61c4ea0de182798a0e5/state index 3e5126c..b6fe829 100644 --- a/87fa3146b90db61c4ea0de182798a0e5/state +++ b/87fa3146b90db61c4ea0de182798a0e5/state @@ -1 +1 @@ -new \ No newline at end of file +backlog \ No newline at end of file From eb7ac21ac85feae7b28861cb4ecf76b84b396d58 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 13:13:59 -0600 Subject: [PATCH 429/489] 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 { From d0a23dc654e771d37657903dafdcd71f115afc02 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 13:38:27 -0600 Subject: [PATCH 430/489] change assignee of issue 31144ca83f6f3de1a9e3db651b70a8b4, None -> seb --- 31144ca83f6f3de1a9e3db651b70a8b4/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 31144ca83f6f3de1a9e3db651b70a8b4/assignee diff --git a/31144ca83f6f3de1a9e3db651b70a8b4/assignee b/31144ca83f6f3de1a9e3db651b70a8b4/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/31144ca83f6f3de1a9e3db651b70a8b4/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From f81eb4845c043e03af57fd1a2db556b69adfe033 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 13:38:33 -0600 Subject: [PATCH 431/489] change state of issue 31144ca83f6f3de1a9e3db651b70a8b4, backlog -> inprogress --- 31144ca83f6f3de1a9e3db651b70a8b4/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/31144ca83f6f3de1a9e3db651b70a8b4/state b/31144ca83f6f3de1a9e3db651b70a8b4/state index b6fe829..505c028 100644 --- a/31144ca83f6f3de1a9e3db651b70a8b4/state +++ b/31144ca83f6f3de1a9e3db651b70a8b4/state @@ -1 +1 @@ -backlog \ No newline at end of file +inprogress \ No newline at end of file From a2717f4a5576f389f92028b6180bbb87b8c414f0 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 13:44:05 -0600 Subject: [PATCH 432/489] add comment 6ce4f668904c7016c846bcc9c0429aca on issue 87fa3146b90db61c4ea0de182798a0e5 --- .../6ce4f668904c7016c846bcc9c0429aca/description | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 87fa3146b90db61c4ea0de182798a0e5/comments/6ce4f668904c7016c846bcc9c0429aca/description diff --git a/87fa3146b90db61c4ea0de182798a0e5/comments/6ce4f668904c7016c846bcc9c0429aca/description b/87fa3146b90db61c4ea0de182798a0e5/comments/6ce4f668904c7016c846bcc9c0429aca/description new file mode 100644 index 0000000..339771b --- /dev/null +++ b/87fa3146b90db61c4ea0de182798a0e5/comments/6ce4f668904c7016c846bcc9c0429aca/description @@ -0,0 +1,15 @@ +`perf` shows that the majority of the time it takes to run "ent anything" +is spent reading the issue database, and the majority of the time it +takes to read the database is spend running `git log` to find the author +and ctime of issues and comments. + +I've reducted this a bit (in the `speed-test` git branch) by running +`git log` half as many times, but the shape of the flame graph remains +the same. + +We've discussed lazy-loading things to avoid doing this work when we don't +need to. For example, if we're trying to look up the tags off issue A, +there's no need to ingest all the comments of issue B. + +Another option is to store Author and Creation-time the normal way, +as files in the issue directory. We'd never have to run `git log` at all. From fc8a8ae30bfb18f0df8fd65c5e57258a5fb3e7a1 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 13:44:28 -0600 Subject: [PATCH 433/489] change assignee of issue 87fa3146b90db61c4ea0de182798a0e5, None -> seb --- 87fa3146b90db61c4ea0de182798a0e5/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 87fa3146b90db61c4ea0de182798a0e5/assignee diff --git a/87fa3146b90db61c4ea0de182798a0e5/assignee b/87fa3146b90db61c4ea0de182798a0e5/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/87fa3146b90db61c4ea0de182798a0e5/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From 863b720b2870ef2c0586420848b2c3319b624790 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 13:44:35 -0600 Subject: [PATCH 434/489] change state of issue 87fa3146b90db61c4ea0de182798a0e5, backlog -> inprogress --- 87fa3146b90db61c4ea0de182798a0e5/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/87fa3146b90db61c4ea0de182798a0e5/state b/87fa3146b90db61c4ea0de182798a0e5/state index b6fe829..505c028 100644 --- a/87fa3146b90db61c4ea0de182798a0e5/state +++ b/87fa3146b90db61c4ea0de182798a0e5/state @@ -1 +1 @@ -backlog \ No newline at end of file +inprogress \ No newline at end of file From b9d57c9cfa2b095c86bcda9b0a42721289d43ae2 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 13:46:21 -0600 Subject: [PATCH 435/489] change state of issue 8ef792a2092e5ba84da3cb4efc3c88c6, inprogress -> done --- 8ef792a2092e5ba84da3cb4efc3c88c6/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/8ef792a2092e5ba84da3cb4efc3c88c6/state b/8ef792a2092e5ba84da3cb4efc3c88c6/state index 505c028..348ebd9 100644 --- a/8ef792a2092e5ba84da3cb4efc3c88c6/state +++ b/8ef792a2092e5ba84da3cb4efc3c88c6/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 1084ad698c1879306b9f1e3d4137bde9f97c9127 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 13:46:21 -0600 Subject: [PATCH 436/489] set done-time of issue 8ef792a2092e5ba84da3cb4efc3c88c6 to 2025-07-22 13:46:21.421286034 -06:00 --- 8ef792a2092e5ba84da3cb4efc3c88c6/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 8ef792a2092e5ba84da3cb4efc3c88c6/done_time diff --git a/8ef792a2092e5ba84da3cb4efc3c88c6/done_time b/8ef792a2092e5ba84da3cb4efc3c88c6/done_time new file mode 100644 index 0000000..52af538 --- /dev/null +++ b/8ef792a2092e5ba84da3cb4efc3c88c6/done_time @@ -0,0 +1 @@ +2025-07-22T13:46:21.421286034-06:00 \ No newline at end of file From 205a2635cd2221ece441e6996b733e4dc8ae0c49 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 14:03:22 -0600 Subject: [PATCH 437/489] edit comment 6ce4f668904c7016c846bcc9c0429aca on issue FIXME --- .../comments/6ce4f668904c7016c846bcc9c0429aca/description | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/87fa3146b90db61c4ea0de182798a0e5/comments/6ce4f668904c7016c846bcc9c0429aca/description b/87fa3146b90db61c4ea0de182798a0e5/comments/6ce4f668904c7016c846bcc9c0429aca/description index 339771b..ec71a9a 100644 --- a/87fa3146b90db61c4ea0de182798a0e5/comments/6ce4f668904c7016c846bcc9c0429aca/description +++ b/87fa3146b90db61c4ea0de182798a0e5/comments/6ce4f668904c7016c846bcc9c0429aca/description @@ -8,8 +8,9 @@ I've reducted this a bit (in the `speed-test` git branch) by running the same. We've discussed lazy-loading things to avoid doing this work when we don't -need to. For example, if we're trying to look up the tags off issue A, +need to. For example, if we're trying to look up the tags of issue A, there's no need to ingest all the comments of issue B. -Another option is to store Author and Creation-time the normal way, -as files in the issue directory. We'd never have to run `git log` at all. +Another option is to store Author and Creation-time the normal way, as +files in the issue directory. We'd never have to run `git log` at all. +I've verified that this makes ent *very* fast. From 836cea85766933818f4de4e11b72801064ed3d16 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 14:04:05 -0600 Subject: [PATCH 438/489] create new issue 50012ba39d8dac21ac122affe92c4160 --- 50012ba39d8dac21ac122affe92c4160/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 50012ba39d8dac21ac122affe92c4160/description diff --git a/50012ba39d8dac21ac122affe92c4160/description b/50012ba39d8dac21ac122affe92c4160/description new file mode 100644 index 0000000..b3817b3 --- /dev/null +++ b/50012ba39d8dac21ac122affe92c4160/description @@ -0,0 +1 @@ +store Author and Creation-time of Issue & Comment as files, don't run `git log` \ No newline at end of file From a6f4da59c7c64622b03dbb7b935d366cb182750d Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 14:04:23 -0600 Subject: [PATCH 439/489] change assignee of issue 50012ba39d8dac21ac122affe92c4160, None -> seb --- 50012ba39d8dac21ac122affe92c4160/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 50012ba39d8dac21ac122affe92c4160/assignee diff --git a/50012ba39d8dac21ac122affe92c4160/assignee b/50012ba39d8dac21ac122affe92c4160/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/50012ba39d8dac21ac122affe92c4160/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From bf1edfb8f5c5b2bb0642d4c6a31c3a4d5e84f0d0 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 14:04:29 -0600 Subject: [PATCH 440/489] change state of issue 50012ba39d8dac21ac122affe92c4160, new -> backlog --- 50012ba39d8dac21ac122affe92c4160/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 50012ba39d8dac21ac122affe92c4160/state diff --git a/50012ba39d8dac21ac122affe92c4160/state b/50012ba39d8dac21ac122affe92c4160/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/50012ba39d8dac21ac122affe92c4160/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 382572267ace841b44a06a08beb5bd21afc0edcb Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 14:04:35 -0600 Subject: [PATCH 441/489] issue 50012ba39d8dac21ac122affe92c4160 add tag perf --- 50012ba39d8dac21ac122affe92c4160/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 50012ba39d8dac21ac122affe92c4160/tags diff --git a/50012ba39d8dac21ac122affe92c4160/tags b/50012ba39d8dac21ac122affe92c4160/tags new file mode 100644 index 0000000..bd14107 --- /dev/null +++ b/50012ba39d8dac21ac122affe92c4160/tags @@ -0,0 +1 @@ +perf From c6739964281d93677515d81a05f721ccea1cf52e Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 14:04:54 -0600 Subject: [PATCH 442/489] issue 95a8190f4bbdcafbb4c72db81dfc2aa6 add tag perf --- 95a8190f4bbdcafbb4c72db81dfc2aa6/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 95a8190f4bbdcafbb4c72db81dfc2aa6/tags diff --git a/95a8190f4bbdcafbb4c72db81dfc2aa6/tags b/95a8190f4bbdcafbb4c72db81dfc2aa6/tags new file mode 100644 index 0000000..bd14107 --- /dev/null +++ b/95a8190f4bbdcafbb4c72db81dfc2aa6/tags @@ -0,0 +1 @@ +perf From b68db15be5429ab9299bf9f9556c37e5b3cdf6ba Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 14:05:06 -0600 Subject: [PATCH 443/489] issue 478ac34c204be06b1da5b4f0b5a2532d add tag perf --- 478ac34c204be06b1da5b4f0b5a2532d/tags | 1 + 1 file changed, 1 insertion(+) diff --git a/478ac34c204be06b1da5b4f0b5a2532d/tags b/478ac34c204be06b1da5b4f0b5a2532d/tags index d287cd3..68a51eb 100644 --- a/478ac34c204be06b1da5b4f0b5a2532d/tags +++ b/478ac34c204be06b1da5b4f0b5a2532d/tags @@ -1 +1,2 @@ debug +perf From fc8f27b2056bd7906c39d5c2e70546177c6f1d6b Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 14:05:38 -0600 Subject: [PATCH 444/489] issue 31144ca83f6f3de1a9e3db651b70a8b4 add tag perf --- 31144ca83f6f3de1a9e3db651b70a8b4/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 31144ca83f6f3de1a9e3db651b70a8b4/tags diff --git a/31144ca83f6f3de1a9e3db651b70a8b4/tags b/31144ca83f6f3de1a9e3db651b70a8b4/tags new file mode 100644 index 0000000..bd14107 --- /dev/null +++ b/31144ca83f6f3de1a9e3db651b70a8b4/tags @@ -0,0 +1 @@ +perf From c8d3c32797e199de209b795a10b3eb1b454d6410 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 14:05:45 -0600 Subject: [PATCH 445/489] issue 87fa3146b90db61c4ea0de182798a0e5 add tag perf --- 87fa3146b90db61c4ea0de182798a0e5/tags | 1 + 1 file changed, 1 insertion(+) diff --git a/87fa3146b90db61c4ea0de182798a0e5/tags b/87fa3146b90db61c4ea0de182798a0e5/tags index 7877337..767ce86 100644 --- a/87fa3146b90db61c4ea0de182798a0e5/tags +++ b/87fa3146b90db61c4ea0de182798a0e5/tags @@ -1 +1,2 @@ issue +perf From a2b3b336bf62368f2311f9fb60d0046ed1466ca0 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 14:06:01 -0600 Subject: [PATCH 446/489] change state of issue 50012ba39d8dac21ac122affe92c4160, backlog -> inprogress --- 50012ba39d8dac21ac122affe92c4160/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/50012ba39d8dac21ac122affe92c4160/state b/50012ba39d8dac21ac122affe92c4160/state index b6fe829..505c028 100644 --- a/50012ba39d8dac21ac122affe92c4160/state +++ b/50012ba39d8dac21ac122affe92c4160/state @@ -1 +1 @@ -backlog \ No newline at end of file +inprogress \ No newline at end of file From cbf391b2475ad74b95758e4e502e80f6f9af18ad Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 14:10:27 -0600 Subject: [PATCH 447/489] edit comment 6ce4f668904c7016c846bcc9c0429aca on issue FIXME --- .../comments/6ce4f668904c7016c846bcc9c0429aca/description | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/87fa3146b90db61c4ea0de182798a0e5/comments/6ce4f668904c7016c846bcc9c0429aca/description b/87fa3146b90db61c4ea0de182798a0e5/comments/6ce4f668904c7016c846bcc9c0429aca/description index ec71a9a..f0fdef8 100644 --- a/87fa3146b90db61c4ea0de182798a0e5/comments/6ce4f668904c7016c846bcc9c0429aca/description +++ b/87fa3146b90db61c4ea0de182798a0e5/comments/6ce4f668904c7016c846bcc9c0429aca/description @@ -13,4 +13,6 @@ there's no need to ingest all the comments of issue B. Another option is to store Author and Creation-time the normal way, as files in the issue directory. We'd never have to run `git log` at all. -I've verified that this makes ent *very* fast. +I've verified that this makes ent *very* fast. This is a much easier +change than lazy-loading. I opened issue 50012ba39d8dac21ac122affe92c4160 +to make this switch. From 45905bcdc2cf5111d72296412e2aa672b537e569 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 14:40:16 -0600 Subject: [PATCH 448/489] create new issue d9a22cb47b2cf0ef56f32674fa65f737 --- d9a22cb47b2cf0ef56f32674fa65f737/description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 d9a22cb47b2cf0ef56f32674fa65f737/description diff --git a/d9a22cb47b2cf0ef56f32674fa65f737/description b/d9a22cb47b2cf0ef56f32674fa65f737/description new file mode 100644 index 0000000..3a70b6d --- /dev/null +++ b/d9a22cb47b2cf0ef56f32674fa65f737/description @@ -0,0 +1,4 @@ +teach `ent tag ISSUE TAG` to add multiple tags at once + +Probably use a syntax like `ent tag ISSUE TAG[,TAG...]` to match the +filter syntax. From f232b549e0bdb902de6ae4826abaf1995a6db62d Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 14:40:30 -0600 Subject: [PATCH 449/489] change state of issue d9a22cb47b2cf0ef56f32674fa65f737, new -> backlog --- d9a22cb47b2cf0ef56f32674fa65f737/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 d9a22cb47b2cf0ef56f32674fa65f737/state diff --git a/d9a22cb47b2cf0ef56f32674fa65f737/state b/d9a22cb47b2cf0ef56f32674fa65f737/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/d9a22cb47b2cf0ef56f32674fa65f737/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 83c70622df744357aeeb8cb48b171936a9d6ef87 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 22 Jul 2025 20:02:12 -0600 Subject: [PATCH 450/489] add comment 783786c019dc6dff582b173dfd1e1d10 on issue 87fa3146b90db61c4ea0de182798a0e5 --- .../comments/783786c019dc6dff582b173dfd1e1d10/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 87fa3146b90db61c4ea0de182798a0e5/comments/783786c019dc6dff582b173dfd1e1d10/description diff --git a/87fa3146b90db61c4ea0de182798a0e5/comments/783786c019dc6dff582b173dfd1e1d10/description b/87fa3146b90db61c4ea0de182798a0e5/comments/783786c019dc6dff582b173dfd1e1d10/description new file mode 100644 index 0000000..41a9dca --- /dev/null +++ b/87fa3146b90db61c4ea0de182798a0e5/comments/783786c019dc6dff582b173dfd1e1d10/description @@ -0,0 +1 @@ +i like storing the author / etc. as files - this was an interesting thread to read RE performance. :) From 13074ee836a8e6d8572cf3823b0692616169ba07 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 22 Jul 2025 20:12:14 -0600 Subject: [PATCH 451/489] add comment 52c8960ce61a41cd1b6e1a79cc059cfb on issue 1f85dfac686d5ea2417b2b07f7e1ff01 --- .../comments/52c8960ce61a41cd1b6e1a79cc059cfb/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 1f85dfac686d5ea2417b2b07f7e1ff01/comments/52c8960ce61a41cd1b6e1a79cc059cfb/description diff --git a/1f85dfac686d5ea2417b2b07f7e1ff01/comments/52c8960ce61a41cd1b6e1a79cc059cfb/description b/1f85dfac686d5ea2417b2b07f7e1ff01/comments/52c8960ce61a41cd1b6e1a79cc059cfb/description new file mode 100644 index 0000000..69154c9 --- /dev/null +++ b/1f85dfac686d5ea2417b2b07f7e1ff01/comments/52c8960ce61a41cd1b6e1a79cc059cfb/description @@ -0,0 +1 @@ +will need an attachments manager, since we probably want to be passing around handles and not actual file data... unless we move the struct remotely? will have to think about it From d1fdf2d87648eaa81dd24f029d71f167b15b9372 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 20:37:03 -0600 Subject: [PATCH 452/489] change state of issue 31144ca83f6f3de1a9e3db651b70a8b4, inprogress -> done --- 31144ca83f6f3de1a9e3db651b70a8b4/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/31144ca83f6f3de1a9e3db651b70a8b4/state b/31144ca83f6f3de1a9e3db651b70a8b4/state index 505c028..348ebd9 100644 --- a/31144ca83f6f3de1a9e3db651b70a8b4/state +++ b/31144ca83f6f3de1a9e3db651b70a8b4/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 654630a692614fd40ad4cbe64c22c01c0f15d3d0 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 20:37:03 -0600 Subject: [PATCH 453/489] set done-time of issue 31144ca83f6f3de1a9e3db651b70a8b4 to 2025-07-22 20:37:03.829541278 -06:00 --- 31144ca83f6f3de1a9e3db651b70a8b4/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 31144ca83f6f3de1a9e3db651b70a8b4/done_time diff --git a/31144ca83f6f3de1a9e3db651b70a8b4/done_time b/31144ca83f6f3de1a9e3db651b70a8b4/done_time new file mode 100644 index 0000000..637f6c2 --- /dev/null +++ b/31144ca83f6f3de1a9e3db651b70a8b4/done_time @@ -0,0 +1 @@ +2025-07-22T20:37:03.829541278-06:00 \ No newline at end of file From ed6c192d9a2dcb2e26325edf1e104901cb827450 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 20:39:43 -0600 Subject: [PATCH 454/489] add dep 50012ba39d8dac21ac122affe92c4160 to issue 87fa3146b90db61c4ea0de182798a0e5 --- .../dependencies/50012ba39d8dac21ac122affe92c4160 | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 87fa3146b90db61c4ea0de182798a0e5/dependencies/50012ba39d8dac21ac122affe92c4160 diff --git a/87fa3146b90db61c4ea0de182798a0e5/dependencies/50012ba39d8dac21ac122affe92c4160 b/87fa3146b90db61c4ea0de182798a0e5/dependencies/50012ba39d8dac21ac122affe92c4160 new file mode 100644 index 0000000..e69de29 From ee3d05f270f27591a3bff2fd3c0c95c71f522e18 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 20:40:57 -0600 Subject: [PATCH 455/489] change assignee of issue 478ac34c204be06b1da5b4f0b5a2532d, None -> seb --- 478ac34c204be06b1da5b4f0b5a2532d/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 478ac34c204be06b1da5b4f0b5a2532d/assignee diff --git a/478ac34c204be06b1da5b4f0b5a2532d/assignee b/478ac34c204be06b1da5b4f0b5a2532d/assignee new file mode 100644 index 0000000..d4596cc --- /dev/null +++ b/478ac34c204be06b1da5b4f0b5a2532d/assignee @@ -0,0 +1 @@ +seb \ No newline at end of file From dfcf014303dfafbf18cf27c033de836709184b23 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 20:41:11 -0600 Subject: [PATCH 456/489] change state of issue 478ac34c204be06b1da5b4f0b5a2532d, backlog -> inprogress --- 478ac34c204be06b1da5b4f0b5a2532d/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/478ac34c204be06b1da5b4f0b5a2532d/state b/478ac34c204be06b1da5b4f0b5a2532d/state index b6fe829..505c028 100644 --- a/478ac34c204be06b1da5b4f0b5a2532d/state +++ b/478ac34c204be06b1da5b4f0b5a2532d/state @@ -1 +1 @@ -backlog \ No newline at end of file +inprogress \ No newline at end of file From 15dc384d782351097645fadf0588b0deac37e371 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 20:45:59 -0600 Subject: [PATCH 457/489] add comment af965d012390c5c18c16a94f0714e720 on issue 478ac34c204be06b1da5b4f0b5a2532d --- .../description | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 478ac34c204be06b1da5b4f0b5a2532d/comments/af965d012390c5c18c16a94f0714e720/description diff --git a/478ac34c204be06b1da5b4f0b5a2532d/comments/af965d012390c5c18c16a94f0714e720/description b/478ac34c204be06b1da5b4f0b5a2532d/comments/af965d012390c5c18c16a94f0714e720/description new file mode 100644 index 0000000..3b1391b --- /dev/null +++ b/478ac34c204be06b1da5b4f0b5a2532d/comments/af965d012390c5c18c16a94f0714e720/description @@ -0,0 +1,18 @@ +I added a simple performance-measuring tool, `tools/time-ent`. It runs +ent a couple of different ways, against a known entomologist-data branch +(specified by a commit in the regular entomologist-data branch in this +repo). That script gives a rough, over-all measurement of how long +different ent commands take. + +More detailed information, including the call tree and flame graph, +can be gathered like this: + +``` +$ perf record -g -F 999 ent tag 7e2a3a59fb6b77403ff1035255367607 +$ perf script -F +pid > /tmp/test.perf +``` + +Then upload the `test.perf` file to the firefox visualizer at +. + +A local visualizer would be nice, i'm sure there is one somewhere. From 5ef3dbbeeeb834e3262ead62fa1d67ba4057872c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 20:47:14 -0600 Subject: [PATCH 458/489] change state of issue 478ac34c204be06b1da5b4f0b5a2532d, inprogress -> done --- 478ac34c204be06b1da5b4f0b5a2532d/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/478ac34c204be06b1da5b4f0b5a2532d/state b/478ac34c204be06b1da5b4f0b5a2532d/state index 505c028..348ebd9 100644 --- a/478ac34c204be06b1da5b4f0b5a2532d/state +++ b/478ac34c204be06b1da5b4f0b5a2532d/state @@ -1 +1 @@ -inprogress \ No newline at end of file +done \ No newline at end of file From 9626dd6e3c68a0ec43104bc1162e86c0c68b7ec9 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 20:47:14 -0600 Subject: [PATCH 459/489] set done-time of issue 478ac34c204be06b1da5b4f0b5a2532d to 2025-07-22 20:47:14.363750382 -06:00 --- 478ac34c204be06b1da5b4f0b5a2532d/done_time | 1 + 1 file changed, 1 insertion(+) create mode 100644 478ac34c204be06b1da5b4f0b5a2532d/done_time diff --git a/478ac34c204be06b1da5b4f0b5a2532d/done_time b/478ac34c204be06b1da5b4f0b5a2532d/done_time new file mode 100644 index 0000000..3c3b9a9 --- /dev/null +++ b/478ac34c204be06b1da5b4f0b5a2532d/done_time @@ -0,0 +1 @@ +2025-07-22T20:47:14.363750382-06:00 \ No newline at end of file From 7410c1ff20d9bc799c017403dfdec34998a3614f Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 22 Jul 2025 20:53:28 -0600 Subject: [PATCH 460/489] add comment 4fae444c41579442b412d8b2de72a8d8 on issue 4a9118e5e06956e0b0766ace15174297 --- .../4fae444c41579442b412d8b2de72a8d8/description | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 4a9118e5e06956e0b0766ace15174297/comments/4fae444c41579442b412d8b2de72a8d8/description diff --git a/4a9118e5e06956e0b0766ace15174297/comments/4fae444c41579442b412d8b2de72a8d8/description b/4a9118e5e06956e0b0766ace15174297/comments/4fae444c41579442b412d8b2de72a8d8/description new file mode 100644 index 0000000..c26403d --- /dev/null +++ b/4a9118e5e06956e0b0766ace15174297/comments/4fae444c41579442b412d8b2de72a8d8/description @@ -0,0 +1,10 @@ +Yeah that red blob 🛑 is super heavy, and the octagonal shape is pretty +subtle - it looks more like a stop light than a stop sign. + +I like your thought of somehow showing the state of the issues we +dependend on... How about showing the count of not-Done dependencies? +And once all the dependencies are Done, not showing the ↖ or ⌛ or +whatever at all. + +In the `ent show` output we could list all dependencies with a ↖ or +⌛ for the not-Done ones and a ✅ for the Done ones. From 15f1b6784d84dcb6ed1b00156f543d740bb702c3 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 22 Jul 2025 22:55:01 -0600 Subject: [PATCH 461/489] create new issue cffd805252f9a2b4373abf7852d9e750 --- cffd805252f9a2b4373abf7852d9e750/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 cffd805252f9a2b4373abf7852d9e750/description diff --git a/cffd805252f9a2b4373abf7852d9e750/description b/cffd805252f9a2b4373abf7852d9e750/description new file mode 100644 index 0000000..d161b17 --- /dev/null +++ b/cffd805252f9a2b4373abf7852d9e750/description @@ -0,0 +1,3 @@ +make generic Entry type which can get put in FS + +make a new type, Entry which allows things to get put into / taken out of the filesystem From bea536841170db3597869d8e88e2f8b2947bbd04 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 22 Jul 2025 22:55:09 -0600 Subject: [PATCH 462/489] change assignee of issue cffd805252f9a2b4373abf7852d9e750, None -> sigil-03 --- cffd805252f9a2b4373abf7852d9e750/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 cffd805252f9a2b4373abf7852d9e750/assignee diff --git a/cffd805252f9a2b4373abf7852d9e750/assignee b/cffd805252f9a2b4373abf7852d9e750/assignee new file mode 100644 index 0000000..284bb3b --- /dev/null +++ b/cffd805252f9a2b4373abf7852d9e750/assignee @@ -0,0 +1 @@ +sigil-03 \ No newline at end of file From 194463f853b68a71f2e98211f34079330e52e826 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 22 Jul 2025 22:55:17 -0600 Subject: [PATCH 463/489] change state of issue cffd805252f9a2b4373abf7852d9e750, new -> inprogress --- cffd805252f9a2b4373abf7852d9e750/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 cffd805252f9a2b4373abf7852d9e750/state diff --git a/cffd805252f9a2b4373abf7852d9e750/state b/cffd805252f9a2b4373abf7852d9e750/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/cffd805252f9a2b4373abf7852d9e750/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From 65c5e317af2e0ed6e0df1fcd21d48078ad4e1fbe Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 22 Jul 2025 22:57:54 -0600 Subject: [PATCH 464/489] add comment aaae75bbc6cb3f687a6a7d84bd4567e5 on issue cffd805252f9a2b4373abf7852d9e750 --- .../aaae75bbc6cb3f687a6a7d84bd4567e5/description | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 cffd805252f9a2b4373abf7852d9e750/comments/aaae75bbc6cb3f687a6a7d84bd4567e5/description diff --git a/cffd805252f9a2b4373abf7852d9e750/comments/aaae75bbc6cb3f687a6a7d84bd4567e5/description b/cffd805252f9a2b4373abf7852d9e750/comments/aaae75bbc6cb3f687a6a7d84bd4567e5/description new file mode 100644 index 0000000..17b5695 --- /dev/null +++ b/cffd805252f9a2b4373abf7852d9e750/comments/aaae75bbc6cb3f687a6a7d84bd4567e5/description @@ -0,0 +1,10 @@ +got the initial framework laid out tonight - some of the naming i'm still not happy with, so it'll take some staring at that to figure out what feels right. + +i'm not entirely sure the direction of... type responsibility..? that this is implementing is correct either, but we'll see. + +right now i have a two test cases, one which is passing, and the other which isn't passing yet, because i still have `todo!()`s for the vec implementation (needs to make a dir) + +dir naming is another thing i'm not totally happy about yet as well. + + +but it's a start! From 95a2c0b5eb558cc1659fccba122e3ccc65552881 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 22 Jul 2025 23:01:36 -0600 Subject: [PATCH 465/489] add comment 8e95fd44281f27fff6d9eaed68f4a6d8 on issue 50012ba39d8dac21ac122affe92c4160 --- .../comments/8e95fd44281f27fff6d9eaed68f4a6d8/description | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 50012ba39d8dac21ac122affe92c4160/comments/8e95fd44281f27fff6d9eaed68f4a6d8/description diff --git a/50012ba39d8dac21ac122affe92c4160/comments/8e95fd44281f27fff6d9eaed68f4a6d8/description b/50012ba39d8dac21ac122affe92c4160/comments/8e95fd44281f27fff6d9eaed68f4a6d8/description new file mode 100644 index 0000000..52ecfa4 --- /dev/null +++ b/50012ba39d8dac21ac122affe92c4160/comments/8e95fd44281f27fff6d9eaed68f4a6d8/description @@ -0,0 +1,7 @@ +i have some work which might take care of this which is happening here: + +entomologist:cffd805252f9a2b4373abf7852d9e750 + +this creates a new type Entry, which has the basic author / creation-time / id as files, and then stores T in the filesystem however it likes to be stored. + +it's still a ways off, but maybe it's worth waiting for so we don't have duplicate work? and for now we can just live with the git log penalty. From 7292c6a39abe3b19004898657ec401b802477caa Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 22 Jul 2025 23:02:00 -0600 Subject: [PATCH 466/489] edit description of issue cffd805252f9a2b4373abf7852d9e750 --- cffd805252f9a2b4373abf7852d9e750/description | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cffd805252f9a2b4373abf7852d9e750/description b/cffd805252f9a2b4373abf7852d9e750/description index d161b17..f46af6b 100644 --- a/cffd805252f9a2b4373abf7852d9e750/description +++ b/cffd805252f9a2b4373abf7852d9e750/description @@ -1,3 +1,6 @@ make generic Entry type which can get put in FS make a new type, Entry which allows things to get put into / taken out of the filesystem + + +branch: `03/entries` From 99f43accdc0ca24ece068c95cc6be20044c772fb Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 23 Jul 2025 17:58:06 -0600 Subject: [PATCH 467/489] add comment d05f01c7b06ef3354ee22dc614560fdd on issue dd20d3ddc86ee802fe7b15e2c91dc160 --- .../d05f01c7b06ef3354ee22dc614560fdd/description | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 dd20d3ddc86ee802fe7b15e2c91dc160/comments/d05f01c7b06ef3354ee22dc614560fdd/description diff --git a/dd20d3ddc86ee802fe7b15e2c91dc160/comments/d05f01c7b06ef3354ee22dc614560fdd/description b/dd20d3ddc86ee802fe7b15e2c91dc160/comments/d05f01c7b06ef3354ee22dc614560fdd/description new file mode 100644 index 0000000..1fd3171 --- /dev/null +++ b/dd20d3ddc86ee802fe7b15e2c91dc160/comments/d05f01c7b06ef3354ee22dc614560fdd/description @@ -0,0 +1,16 @@ +Another option is to escape '/' in tags when turning them into filenames. + +Let's somewhat arbitrarily say we use ',' as the escape character. + +',' in a tag would be replaced with the two-character sequence ',0' +in the filename. + +'/' in a tag would be replaced with ',1' in the filename. + +(And so on for any other characters we need to escape.) + +This would give the user the behavior we want, which is to be free to use +'/' in tags. + +The implementation detail of escaped tags would be hidden from the user +unless they dug into the on-disk format by hand. From e1287514f65fcc8052a4627170dc2c8018a8a353 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 23 Jul 2025 15:26:42 -0600 Subject: [PATCH 468/489] switch to pretty_assertions, makes it much easier to tell what blew up --- Cargo.toml | 3 +++ src/comment.rs | 1 + src/git.rs | 1 + src/issue.rs | 1 + src/issues.rs | 1 + 5 files changed, 7 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 864691a..4d2d2c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,9 @@ edition = "2024" default = [] log = ["dep:log", "dep:simple_logger"] +[dev-dependencies] +pretty_assertions = "1.4.1" + [dependencies] anyhow = "1.0.95" chrono = "0.4.41" diff --git a/src/comment.rs b/src/comment.rs index e042f63..9770d65 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -208,6 +208,7 @@ impl Comment { #[cfg(test)] mod tests { use super::*; + use pretty_assertions::assert_eq; #[test] fn read_comment_0() { diff --git a/src/git.rs b/src/git.rs index 3a03bac..6e70fa8 100644 --- a/src/git.rs +++ b/src/git.rs @@ -502,6 +502,7 @@ fn create_orphan_branch_at_path( #[cfg(test)] mod tests { use super::*; + use pretty_assertions::assert_eq; #[test] fn test_worktree() { diff --git a/src/issue.rs b/src/issue.rs index e3bf9d3..4d82deb 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -548,6 +548,7 @@ impl Issue { #[cfg(test)] mod tests { use super::*; + use pretty_assertions::assert_eq; #[test] fn read_issue_0() { diff --git a/src/issues.rs b/src/issues.rs index a01f41c..7c43e43 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -87,6 +87,7 @@ impl Issues { #[cfg(test)] mod tests { use super::*; + use pretty_assertions::assert_eq; #[test] fn read_issues_0000() { From 7abcf2e4466d95464252c036743cda3a757726a9 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 23 Jul 2025 18:45:20 -0600 Subject: [PATCH 469/489] sort issue tags This will be useful testing (and general consistency) when tags are files in a directory instead of lines in a file, and thus subject to random directory order. --- src/issue.rs | 3 ++- src/issues.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/issue.rs b/src/issue.rs index 4d82deb..6f5889e 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -143,6 +143,7 @@ impl Issue { .filter(|s| s.len() > 0) .map(|tag| String::from(tag.trim())) .collect(); + tags.sort(); } else if file_name == "comments" && direntry.metadata()?.is_dir() { Self::read_comments(&mut comments, &direntry.path())?; } else { @@ -562,9 +563,9 @@ mod tests { .with_timezone(&chrono::Local), done_time: None, tags: Vec::::from([ - String::from("tag1"), String::from("TAG2"), String::from("i-am-also-a-tag"), + String::from("tag1"), ]), state: State::New, dependencies: None, diff --git a/src/issues.rs b/src/issues.rs index 7c43e43..fc182e7 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -127,9 +127,9 @@ mod tests { .with_timezone(&chrono::Local), done_time: None, tags: Vec::::from([ - String::from("tag1"), String::from("TAG2"), - String::from("i-am-also-a-tag") + String::from("i-am-also-a-tag"), + String::from("tag1"), ]), state: crate::issue::State::New, dependencies: None, From ef8a648cf8d6f60ac82c0017bec930459e3a76f3 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 24 Jul 2025 08:36:25 -0600 Subject: [PATCH 470/489] test dir cleanup: rename test/0000/3943fc5c173fdf41c0a22251593cd476 Renaming everything also means they have new creation-times, since we're now git logging a different file/dir. --- src/issue.rs | 6 +++--- src/issues.rs | 4 ++-- .../description | 0 .../tags | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename test/0000/{3943fc5c173fdf41c0a22251593cd476d96e6c9f => 3943fc5c173fdf41c0a22251593cd476}/description (100%) rename test/0000/{3943fc5c173fdf41c0a22251593cd476d96e6c9f => 3943fc5c173fdf41c0a22251593cd476}/tags (100%) diff --git a/src/issue.rs b/src/issue.rs index 6f5889e..3e13a7d 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -553,12 +553,12 @@ mod tests { #[test] fn read_issue_0() { - let issue_dir = std::path::Path::new("test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/"); + let issue_dir = std::path::Path::new("test/0000/3943fc5c173fdf41c0a22251593cd476/"); let issue = Issue::new_from_dir(issue_dir).unwrap(); let expected = Issue { - id: String::from("3943fc5c173fdf41c0a22251593cd476d96e6c9f"), + id: String::from("3943fc5c173fdf41c0a22251593cd476"), author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-24T08:36:25-06:00") .unwrap() .with_timezone(&chrono::Local), done_time: None, diff --git a/src/issues.rs b/src/issues.rs index fc182e7..11cb233 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -115,14 +115,14 @@ mod tests { dir, }); - let uuid = String::from("3943fc5c173fdf41c0a22251593cd476d96e6c9f"); + let uuid = String::from("3943fc5c173fdf41c0a22251593cd476"); let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); expected.add_issue( crate::issue::Issue { id: uuid, author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-24T08:36:25-06:00") .unwrap() .with_timezone(&chrono::Local), done_time: None, diff --git a/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/description b/test/0000/3943fc5c173fdf41c0a22251593cd476/description similarity index 100% rename from test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/description rename to test/0000/3943fc5c173fdf41c0a22251593cd476/description diff --git a/test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/tags b/test/0000/3943fc5c173fdf41c0a22251593cd476/tags similarity index 100% rename from test/0000/3943fc5c173fdf41c0a22251593cd476d96e6c9f/tags rename to test/0000/3943fc5c173fdf41c0a22251593cd476/tags From 4683760942d33f99c50e321c3a901c0fc619da96 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 24 Jul 2025 08:37:07 -0600 Subject: [PATCH 471/489] test dir cleanup: rename test/0000/7792b063eef6d33e7da5dc1856750c14 Renaming everything also means they have new creation-times, since we're now git logging a different file/dir. --- src/issue.rs | 6 +++--- src/issues.rs | 4 ++-- .../assignee | 0 .../description | 0 .../state | 0 5 files changed, 5 insertions(+), 5 deletions(-) rename test/0000/{7792b063eef6d33e7da5dc1856750c149ba678c6 => 7792b063eef6d33e7da5dc1856750c14}/assignee (100%) rename test/0000/{7792b063eef6d33e7da5dc1856750c149ba678c6 => 7792b063eef6d33e7da5dc1856750c14}/description (100%) rename test/0000/{7792b063eef6d33e7da5dc1856750c149ba678c6 => 7792b063eef6d33e7da5dc1856750c14}/state (100%) diff --git a/src/issue.rs b/src/issue.rs index 3e13a7d..06f959f 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -581,12 +581,12 @@ mod tests { #[test] fn read_issue_1() { - let issue_dir = std::path::Path::new("test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/"); + let issue_dir = std::path::Path::new("test/0000/7792b063eef6d33e7da5dc1856750c14/"); let issue = Issue::new_from_dir(issue_dir).unwrap(); let expected = Issue { - id: String::from("7792b063eef6d33e7da5dc1856750c149ba678c6"), + id: String::from("7792b063eef6d33e7da5dc1856750c14"), author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-24T08:37:07-06:00") .unwrap() .with_timezone(&chrono::Local), done_time: None, diff --git a/src/issues.rs b/src/issues.rs index 11cb233..db32737 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -96,13 +96,13 @@ mod tests { let mut expected = Issues::new(); - let uuid = String::from("7792b063eef6d33e7da5dc1856750c149ba678c6"); + let uuid = String::from("7792b063eef6d33e7da5dc1856750c14"); let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); expected.add_issue(crate::issue::Issue { id: uuid, author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-24T08:37:07-06:00") .unwrap() .with_timezone(&chrono::Local), done_time: None, diff --git a/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/assignee b/test/0000/7792b063eef6d33e7da5dc1856750c14/assignee similarity index 100% rename from test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/assignee rename to test/0000/7792b063eef6d33e7da5dc1856750c14/assignee diff --git a/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/description b/test/0000/7792b063eef6d33e7da5dc1856750c14/description similarity index 100% rename from test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/description rename to test/0000/7792b063eef6d33e7da5dc1856750c14/description diff --git a/test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/state b/test/0000/7792b063eef6d33e7da5dc1856750c14/state similarity index 100% rename from test/0000/7792b063eef6d33e7da5dc1856750c149ba678c6/state rename to test/0000/7792b063eef6d33e7da5dc1856750c14/state From 694d127638842de94ef646819be71e1e1bb86255 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 24 Jul 2025 08:37:46 -0600 Subject: [PATCH 472/489] test dir cleanup: rename test/0001/3fa5bfd93317ad25772680071d5ac325 Renaming everything also means they have new creation-times, since we're now git logging a different file/dir. --- src/issues.rs | 4 ++-- .../description | 0 .../done_time | 0 .../state | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename test/0001/{3fa5bfd93317ad25772680071d5ac3259cd2384f => 3fa5bfd93317ad25772680071d5ac325}/description (100%) rename test/0001/{3fa5bfd93317ad25772680071d5ac3259cd2384f => 3fa5bfd93317ad25772680071d5ac325}/done_time (100%) rename test/0001/{3fa5bfd93317ad25772680071d5ac3259cd2384f => 3fa5bfd93317ad25772680071d5ac325}/state (100%) diff --git a/src/issues.rs b/src/issues.rs index db32737..bd8d687 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -149,13 +149,13 @@ mod tests { let mut expected = Issues::new(); - let uuid = String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"); + let uuid = String::from("3fa5bfd93317ad25772680071d5ac325"); let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); expected.add_issue(crate::issue::Issue { id: uuid, author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-24T08:37:46-06:00") .unwrap() .with_timezone(&chrono::Local), done_time: Some( diff --git a/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/description b/test/0001/3fa5bfd93317ad25772680071d5ac325/description similarity index 100% rename from test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/description rename to test/0001/3fa5bfd93317ad25772680071d5ac325/description diff --git a/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/done_time b/test/0001/3fa5bfd93317ad25772680071d5ac325/done_time similarity index 100% rename from test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/done_time rename to test/0001/3fa5bfd93317ad25772680071d5ac325/done_time diff --git a/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/state b/test/0001/3fa5bfd93317ad25772680071d5ac325/state similarity index 100% rename from test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/state rename to test/0001/3fa5bfd93317ad25772680071d5ac325/state From 05c7c6f4416f23d8d71b54e80ca2a0683bad2b2c Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 24 Jul 2025 10:08:24 -0600 Subject: [PATCH 473/489] test dir cleanup: rename test/0001/dd79c8cfb8beeacd0460429944b4ecbe Renaming everything also means they have new creation-times, since we're now git logging a different file/dir. fixup test/0001/dd79c8cfb8beeacd0460429944b4ecbe, no comment yet --- src/issues.rs | 15 +++------------ .../description | 0 .../state | 0 3 files changed, 3 insertions(+), 12 deletions(-) rename test/0001/{dd79c8cfb8beeacd0460429944b4ecbe95a31561 => dd79c8cfb8beeacd0460429944b4ecbe}/description (100%) rename test/0001/{dd79c8cfb8beeacd0460429944b4ecbe95a31561 => dd79c8cfb8beeacd0460429944b4ecbe}/state (100%) diff --git a/src/issues.rs b/src/issues.rs index bd8d687..e8b759f 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -172,28 +172,19 @@ mod tests { dir, }); - let uuid = String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"); + let uuid = String::from("dd79c8cfb8beeacd0460429944b4ecbe"); let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); let mut comment_dir = dir.clone(); let comment_uuid = String::from("9055dac36045fe36545bed7ae7b49347"); comment_dir.push("comments"); comment_dir.push(&comment_uuid); - let mut expected_comments = Vec::::new(); - expected_comments.push( - crate::comment::Comment { - uuid: comment_uuid, - author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00").unwrap().with_timezone(&chrono::Local), - description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561\n\nIt has multiple lines\n"), - dir: std::path::PathBuf::from(comment_dir), - } - ); + let expected_comments = Vec::::new(); expected.add_issue( crate::issue::Issue { id: uuid, author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-24T10:08:24-06:00") .unwrap() .with_timezone(&chrono::Local), done_time: None, diff --git a/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe/description similarity index 100% rename from test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description rename to test/0001/dd79c8cfb8beeacd0460429944b4ecbe/description diff --git a/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/state b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe/state similarity index 100% rename from test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/state rename to test/0001/dd79c8cfb8beeacd0460429944b4ecbe/state From 598f4e5df838618584ca81e1edceb9d6791b228e Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 24 Jul 2025 10:08:38 -0600 Subject: [PATCH 474/489] test dir cleanup: rename test/0001/dd79c8cfb8beeacd0460429944b4ecbe comment Renaming everything also means they have new creation-times, since we're now git logging a different file/dir. --- src/comment.rs | 9 +++++---- src/issues.rs | 11 ++++++++++- .../9055dac36045fe36545bed7ae7b49347/description | 3 +++ .../9055dac36045fe36545bed7ae7b49347/description | 3 --- 4 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 test/0001/dd79c8cfb8beeacd0460429944b4ecbe/comments/9055dac36045fe36545bed7ae7b49347/description delete mode 100644 test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/comments/9055dac36045fe36545bed7ae7b49347/description diff --git a/src/comment.rs b/src/comment.rs index 9770d65..1fa2e36 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -212,16 +212,17 @@ mod tests { #[test] fn read_comment_0() { - let comment_dir = - std::path::Path::new("test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/comments/9055dac36045fe36545bed7ae7b49347"); + let comment_dir = std::path::Path::new( + "test/0001/dd79c8cfb8beeacd0460429944b4ecbe/comments/9055dac36045fe36545bed7ae7b49347", + ); let comment = Comment::new_from_dir(comment_dir).unwrap(); let expected = Comment { uuid: String::from("9055dac36045fe36545bed7ae7b49347"), author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-24T10:08:38-06:00") .unwrap() .with_timezone(&chrono::Local), - description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561\n\nIt has multiple lines\n"), + description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe\n\nIt has multiple lines\n"), dir: std::path::PathBuf::from(comment_dir), }; assert_eq!(comment, expected); diff --git a/src/issues.rs b/src/issues.rs index e8b759f..8dca0cd 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -179,7 +179,16 @@ mod tests { let comment_uuid = String::from("9055dac36045fe36545bed7ae7b49347"); comment_dir.push("comments"); comment_dir.push(&comment_uuid); - let expected_comments = Vec::::new(); + let mut expected_comments = Vec::::new(); + expected_comments.push( + crate::comment::Comment { + uuid: comment_uuid, + author: String::from("Sebastian Kuzminsky "), + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-24T10:08:38-06:00").unwrap().with_timezone(&chrono::Local), + description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe\n\nIt has multiple lines\n"), + dir: std::path::PathBuf::from(comment_dir), + } + ); expected.add_issue( crate::issue::Issue { id: uuid, diff --git a/test/0001/dd79c8cfb8beeacd0460429944b4ecbe/comments/9055dac36045fe36545bed7ae7b49347/description b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe/comments/9055dac36045fe36545bed7ae7b49347/description new file mode 100644 index 0000000..daa3d62 --- /dev/null +++ b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe/comments/9055dac36045fe36545bed7ae7b49347/description @@ -0,0 +1,3 @@ +This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe + +It has multiple lines diff --git a/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/comments/9055dac36045fe36545bed7ae7b49347/description b/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/comments/9055dac36045fe36545bed7ae7b49347/description deleted file mode 100644 index f9de678..0000000 --- a/test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/comments/9055dac36045fe36545bed7ae7b49347/description +++ /dev/null @@ -1,3 +0,0 @@ -This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561 - -It has multiple lines From b3f5aaeb76652989c82cb9d2d655e8fec23cc81d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 24 Jul 2025 08:38:40 -0600 Subject: [PATCH 475/489] test dir cleanup: rename test/0002/3fa5bfd93317ad25772680071d5ac325 Renaming everything also means they have new creation-times, since we're now git logging a different file/dir. --- src/issues.rs | 4 ++-- .../description | 0 .../state | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename test/0002/{3fa5bfd93317ad25772680071d5ac3259cd2384f => 3fa5bfd93317ad25772680071d5ac325}/description (100%) rename test/0002/{3fa5bfd93317ad25772680071d5ac3259cd2384f => 3fa5bfd93317ad25772680071d5ac325}/state (100%) diff --git a/src/issues.rs b/src/issues.rs index 8dca0cd..e991880 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -216,13 +216,13 @@ mod tests { let mut expected = Issues::new(); - let uuid = String::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"); + let uuid = String::from("3fa5bfd93317ad25772680071d5ac325"); let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); expected.add_issue(crate::issue::Issue { id: uuid, author: String::from("sigil-03 "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-24T08:38:40-06:00") .unwrap() .with_timezone(&chrono::Local), done_time: None, diff --git a/test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/description b/test/0002/3fa5bfd93317ad25772680071d5ac325/description similarity index 100% rename from test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/description rename to test/0002/3fa5bfd93317ad25772680071d5ac325/description diff --git a/test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/state b/test/0002/3fa5bfd93317ad25772680071d5ac325/state similarity index 100% rename from test/0002/3fa5bfd93317ad25772680071d5ac3259cd2384f/state rename to test/0002/3fa5bfd93317ad25772680071d5ac325/state From b3903a9ed2e25214239ae796878c2ba9adc559bd Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 24 Jul 2025 08:39:02 -0600 Subject: [PATCH 476/489] test dir cleanup: rename test/0002/a85f81fc5f14cb5d4851dd445dc9744c Renaming everything also means they have new creation-times, since we're now git logging a different file/dir. --- src/issues.rs | 8 ++++---- .../dependencies/3fa5bfd93317ad25772680071d5ac325} | 0 .../dependencies/dd79c8cfb8beeacd0460429944b4ecbe} | 0 .../description | 0 .../state | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename test/0002/{a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies/3fa5bfd93317ad25772680071d5ac3259cd2384f => a85f81fc5f14cb5d4851dd445dc9744c/dependencies/3fa5bfd93317ad25772680071d5ac325} (100%) rename test/0002/{a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies/dd79c8cfb8beeacd0460429944b4ecbe95a31561 => a85f81fc5f14cb5d4851dd445dc9744c/dependencies/dd79c8cfb8beeacd0460429944b4ecbe} (100%) rename test/0002/{a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7 => a85f81fc5f14cb5d4851dd445dc9744c}/description (100%) rename test/0002/{a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7 => a85f81fc5f14cb5d4851dd445dc9744c}/state (100%) diff --git a/src/issues.rs b/src/issues.rs index e991880..f54fe87 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -256,22 +256,22 @@ mod tests { }, ); - let uuid = String::from("a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7"); + let uuid = String::from("a85f81fc5f14cb5d4851dd445dc9744c"); let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); expected.add_issue( crate::issue::Issue { id: uuid, author: String::from("sigil-03 "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-24T08:39:02-06:00") .unwrap() .with_timezone(&chrono::Local), done_time: None, tags: Vec::::new(), state: crate::issue::State::WontDo, dependencies: Some(vec![ - crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), - crate::issue::IssueHandle::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), + crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac325"), + crate::issue::IssueHandle::from("dd79c8cfb8beeacd0460429944b4ecbe"), ]), assignee: None, description: String::from("issue with dependencies\n\na test has begun\nfor dependencies we seek\nintertwining life"), diff --git a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies/3fa5bfd93317ad25772680071d5ac3259cd2384f b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c/dependencies/3fa5bfd93317ad25772680071d5ac325 similarity index 100% rename from test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies/3fa5bfd93317ad25772680071d5ac3259cd2384f rename to test/0002/a85f81fc5f14cb5d4851dd445dc9744c/dependencies/3fa5bfd93317ad25772680071d5ac325 diff --git a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies/dd79c8cfb8beeacd0460429944b4ecbe95a31561 b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c/dependencies/dd79c8cfb8beeacd0460429944b4ecbe similarity index 100% rename from test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/dependencies/dd79c8cfb8beeacd0460429944b4ecbe95a31561 rename to test/0002/a85f81fc5f14cb5d4851dd445dc9744c/dependencies/dd79c8cfb8beeacd0460429944b4ecbe diff --git a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/description b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c/description similarity index 100% rename from test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/description rename to test/0002/a85f81fc5f14cb5d4851dd445dc9744c/description diff --git a/test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/state b/test/0002/a85f81fc5f14cb5d4851dd445dc9744c/state similarity index 100% rename from test/0002/a85f81fc5f14cb5d4851dd445dc9744c7f16ccc7/state rename to test/0002/a85f81fc5f14cb5d4851dd445dc9744c/state From fad23ba233adc90a34175281552c5eadace9f661 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 24 Jul 2025 08:39:20 -0600 Subject: [PATCH 477/489] test dir cleanup: rename test/0002/dd79c8cfb8beeacd0460429944b4ecbe Renaming everything also means they have new creation-times, since we're now git logging a different file/dir. --- src/issues.rs | 4 ++-- .../description | 0 .../state | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename test/0002/{dd79c8cfb8beeacd0460429944b4ecbe95a31561 => dd79c8cfb8beeacd0460429944b4ecbe}/description (100%) rename test/0002/{dd79c8cfb8beeacd0460429944b4ecbe95a31561 => dd79c8cfb8beeacd0460429944b4ecbe}/state (100%) diff --git a/src/issues.rs b/src/issues.rs index f54fe87..d3c57c0 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -235,14 +235,14 @@ mod tests { dir, }); - let uuid = String::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"); + let uuid = String::from("dd79c8cfb8beeacd0460429944b4ecbe"); let mut dir = std::path::PathBuf::from(issues_dir); dir.push(&uuid); expected.add_issue( crate::issue::Issue { id: uuid, author: String::from("sigil-03 "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") + creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-24T08:39:20-06:00") .unwrap() .with_timezone(&chrono::Local), done_time: None, diff --git a/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description b/test/0002/dd79c8cfb8beeacd0460429944b4ecbe/description similarity index 100% rename from test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/description rename to test/0002/dd79c8cfb8beeacd0460429944b4ecbe/description diff --git a/test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/state b/test/0002/dd79c8cfb8beeacd0460429944b4ecbe/state similarity index 100% rename from test/0002/dd79c8cfb8beeacd0460429944b4ecbe95a31561/state rename to test/0002/dd79c8cfb8beeacd0460429944b4ecbe/state From d877e8f8af81b13765dfd666b8bc8f9936e92816 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 24 Jul 2025 11:58:37 -0600 Subject: [PATCH 478/489] add comment 7fc0142ef391d3824de29d23ae807f2b on issue dd20d3ddc86ee802fe7b15e2c91dc160 --- .../description | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 dd20d3ddc86ee802fe7b15e2c91dc160/comments/7fc0142ef391d3824de29d23ae807f2b/description diff --git a/dd20d3ddc86ee802fe7b15e2c91dc160/comments/7fc0142ef391d3824de29d23ae807f2b/description b/dd20d3ddc86ee802fe7b15e2c91dc160/comments/7fc0142ef391d3824de29d23ae807f2b/description new file mode 100644 index 0000000..dff86e5 --- /dev/null +++ b/dd20d3ddc86ee802fe7b15e2c91dc160/comments/7fc0142ef391d3824de29d23ae807f2b/description @@ -0,0 +1,49 @@ +We've discussed three options for how to store tags: + +1. Each tag is a line in the file "${ISSUE}/tags". This is what we + use now. + + This one is bad because git conflicts are inevitable when people + add/remove tags near each other. This problem is what's prompting + this whole issue. + +2. Each tag is a file in the directory "${ISSUE}/tags/", with characters + that are illegal in filenames (notably '/') escaped. For example, + the tag "tui/v0.1" would live in "${ISSUE}/tags/tui,1v0.1". + + This one looks a bit weird. + +3. Each tag is a file somewhere under the directory "${ISSUE}/tags/", + and tags that include the character '/' live in subdirectories + matching the tag name. For example, the tag "tui/v0.1" would live in + "${ISSUE}/tags/tui/v0.1". + + I like this option but i'm a bit unsure of how some aspects would + work... + +How should entomologist handle "${ISSUE}/tags/tui/v0.1"? Does this +issue have one tag ("tui/v0.1") or two tags ("tui" and "tui/v0.1")? + +If a user runs `ent tag ${ISSUE} tui/v0.1`, i think it would be a bit +surprising if the issue now has both "tui/v0.1" like they asked for, +and also "tui" which they didn't ask for. But maybe that's just how +hierarchical tags work? + +What happens if the user runs "ent tag ${ISSUE} tui/v0.1", then "ent +tag ${ISSUE} -tui" to remove "tui"? Does entomologist refuse, or does +it also remove "tui/v0.1"? Probably it refuses, again because that's +how hierarchical tags work - you (obviously?) can't have "tui/v0.1" +without also having "tui". + +What happens if the user runs "ent tag ${ISSUE} tui/v0.1", then runs +"ent tag ${ISSUE} -tui/v0.1"? Let's assume "v0.1" is the only file in +the "tags/tui/" directory. Probably we remove the "tags/tui" directory, +and now the issue has no tags? Again a bit surprising imo. + +If a user adds a tag "tui", it creates the file "${ISSUE}/tags/tui". +If the user then adds "tui/v0.1" entomologist will have to delete the +"tui" file and replace it with a "tui/" directory, then add a "v0.1" +file to that directory, but that's no big deal. + +If one user adds the tag "tui", and another user adds the tag(s) +"tui/v0.1", there will be a git conflict on sync. From e46dd63957551fc3d2bb7a66330b4a6a311921b5 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 26 Jul 2025 12:55:52 -0600 Subject: [PATCH 479/489] create new issue 131504dd52f3a9b358bfb4701e656fbe --- 131504dd52f3a9b358bfb4701e656fbe/description | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 131504dd52f3a9b358bfb4701e656fbe/description diff --git a/131504dd52f3a9b358bfb4701e656fbe/description b/131504dd52f3a9b358bfb4701e656fbe/description new file mode 100644 index 0000000..3c53fbd --- /dev/null +++ b/131504dd52f3a9b358bfb4701e656fbe/description @@ -0,0 +1,15 @@ +teach `ent sync` to scan `main` branch commits for special commands + +I'm thinking primarily of "Fixes: abc123" but maybe there are others? + +ent could be informed of the main branch name via +entomologist-data:config.toml, and use a special tag pointing at the +most recent commit in that branch that it has processed. When you run +`ent sync`, after it runs `git fetch` it could log the main branch from +its tagged commit to the just-fetched branch head, read all the commit +messages, and find and process all the ent commands. Then finally move +its tag to the new main branch head. + +This way you could put an ent command like "Fixes: abc123" in a bugfix +commit, and after you merge that branch to main, the next ent sync will +mark issue abc123 as done for you. From eb311eccdce488a3a69322fc5c234188e88d533f Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 26 Jul 2025 13:20:23 -0600 Subject: [PATCH 480/489] change state of issue 131504dd52f3a9b358bfb4701e656fbe, new -> backlog --- 131504dd52f3a9b358bfb4701e656fbe/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 131504dd52f3a9b358bfb4701e656fbe/state diff --git a/131504dd52f3a9b358bfb4701e656fbe/state b/131504dd52f3a9b358bfb4701e656fbe/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/131504dd52f3a9b358bfb4701e656fbe/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From a7fd3afb4cd9433efb56983a972fb63b77138731 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 26 Jul 2025 13:20:29 -0600 Subject: [PATCH 481/489] change state of issue 9f0d77fa44e265a4e316f94be42554a5, new -> backlog --- 9f0d77fa44e265a4e316f94be42554a5/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 9f0d77fa44e265a4e316f94be42554a5/state diff --git a/9f0d77fa44e265a4e316f94be42554a5/state b/9f0d77fa44e265a4e316f94be42554a5/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/9f0d77fa44e265a4e316f94be42554a5/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 7c1dc54d4d7798f9ed37d7158a0194d55f0d89d3 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 26 Jul 2025 13:23:06 -0600 Subject: [PATCH 482/489] create new issue 6418c1d923b740057a6138e16664b3c6 --- 6418c1d923b740057a6138e16664b3c6/description | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 6418c1d923b740057a6138e16664b3c6/description diff --git a/6418c1d923b740057a6138e16664b3c6/description b/6418c1d923b740057a6138e16664b3c6/description new file mode 100644 index 0000000..63c3ff5 --- /dev/null +++ b/6418c1d923b740057a6138e16664b3c6/description @@ -0,0 +1,7 @@ +add `ent modify ISSUE CHANGE... + +CHANGE is one or more changes to make to the issue, that looks a bit +like the FILTER of `ent list`. Something like: +``` +$ ent modify 123abc state=inprogress assignee=seb tag=bug,cli +``` From f5f01cb0da4880f1a1103688862e1bc3505635ea Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 26 Jul 2025 13:23:30 -0600 Subject: [PATCH 483/489] edit description of issue 6418c1d923b740057a6138e16664b3c6 --- 6418c1d923b740057a6138e16664b3c6/description | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/6418c1d923b740057a6138e16664b3c6/description b/6418c1d923b740057a6138e16664b3c6/description index 63c3ff5..a9ddf25 100644 --- a/6418c1d923b740057a6138e16664b3c6/description +++ b/6418c1d923b740057a6138e16664b3c6/description @@ -1,4 +1,4 @@ -add `ent modify ISSUE CHANGE... +add `ent modify ISSUE CHANGE...` CHANGE is one or more changes to make to the issue, that looks a bit like the FILTER of `ent list`. Something like: From f1069061cdd66d2aa91b6507a11047172c46c7bd Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 26 Jul 2025 13:23:36 -0600 Subject: [PATCH 484/489] change state of issue 6418c1d923b740057a6138e16664b3c6, new -> backlog --- 6418c1d923b740057a6138e16664b3c6/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 6418c1d923b740057a6138e16664b3c6/state diff --git a/6418c1d923b740057a6138e16664b3c6/state b/6418c1d923b740057a6138e16664b3c6/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/6418c1d923b740057a6138e16664b3c6/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 6db4620c67292fb1120afdbbe666497d7538ade5 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 26 Jul 2025 13:25:38 -0600 Subject: [PATCH 485/489] create new issue e43d29638fa3836087794c669cde18a9 --- e43d29638fa3836087794c669cde18a9/description | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 e43d29638fa3836087794c669cde18a9/description diff --git a/e43d29638fa3836087794c669cde18a9/description b/e43d29638fa3836087794c669cde18a9/description new file mode 100644 index 0000000..f32831d --- /dev/null +++ b/e43d29638fa3836087794c669cde18a9/description @@ -0,0 +1,7 @@ +add a Workflow section to the README + +This section will describe the typical workflows that ent does well. + +This was one thing i found lacking when investigating alternative issue +tracking systems before starting ent, and i think doing this well could +help people start using ent. From 1160ad7f0899c03d14b25002ba42bf9245e08190 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 26 Jul 2025 13:25:48 -0600 Subject: [PATCH 486/489] change state of issue e43d29638fa3836087794c669cde18a9, new -> backlog --- e43d29638fa3836087794c669cde18a9/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 e43d29638fa3836087794c669cde18a9/state diff --git a/e43d29638fa3836087794c669cde18a9/state b/e43d29638fa3836087794c669cde18a9/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/e43d29638fa3836087794c669cde18a9/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From d37c0a0af130698162b5a104a90ffb92ae73d17b Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 30 Jul 2025 11:05:24 -0600 Subject: [PATCH 487/489] add comment d35ca7f72272a79938f4189828c36662 on issue bed47b2be016cc41eb43ef6d0acf1f8f --- .../comments/d35ca7f72272a79938f4189828c36662/description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 bed47b2be016cc41eb43ef6d0acf1f8f/comments/d35ca7f72272a79938f4189828c36662/description diff --git a/bed47b2be016cc41eb43ef6d0acf1f8f/comments/d35ca7f72272a79938f4189828c36662/description b/bed47b2be016cc41eb43ef6d0acf1f8f/comments/d35ca7f72272a79938f4189828c36662/description new file mode 100644 index 0000000..d1e0870 --- /dev/null +++ b/bed47b2be016cc41eb43ef6d0acf1f8f/comments/d35ca7f72272a79938f4189828c36662/description @@ -0,0 +1,4 @@ +just came to the repo to make this issue if it didn't already exist, so +1 to this + + +it would be very useful to be able to assign things to multiple people From afa327c73e77450899b73e5b96fd57fd032074ea Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 30 Jul 2025 11:48:06 -0600 Subject: [PATCH 488/489] create new issue 779724be18a248a7f13fb52243b1e779 --- 779724be18a248a7f13fb52243b1e779/description | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 779724be18a248a7f13fb52243b1e779/description diff --git a/779724be18a248a7f13fb52243b1e779/description b/779724be18a248a7f13fb52243b1e779/description new file mode 100644 index 0000000..6aa6a07 --- /dev/null +++ b/779724be18a248a7f13fb52243b1e779/description @@ -0,0 +1,12 @@ +`git clone; ent list` fails + +There's no local entomologist branch: +``` +$ ent list +stdout: +stderr: fatal: invalid reference: entomologist-data + +Error: Oops, something went wrong +``` + +Running `ent sync` fixes is, but entomologist should be more resilient. From e16d97d5f778c5c5ad0abbb86ce233b18bc600dc Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Wed, 30 Jul 2025 13:54:56 -0600 Subject: [PATCH 489/489] add comment 02ecad639e8781f007f24fec4fb85a84 on issue 01b9499f6a927ca6326b91037bfe0821 --- .../comments/02ecad639e8781f007f24fec4fb85a84/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 01b9499f6a927ca6326b91037bfe0821/comments/02ecad639e8781f007f24fec4fb85a84/description diff --git a/01b9499f6a927ca6326b91037bfe0821/comments/02ecad639e8781f007f24fec4fb85a84/description b/01b9499f6a927ca6326b91037bfe0821/comments/02ecad639e8781f007f24fec4fb85a84/description new file mode 100644 index 0000000..92b7372 --- /dev/null +++ b/01b9499f6a927ca6326b91037bfe0821/comments/02ecad639e8781f007f24fec4fb85a84/description @@ -0,0 +1 @@ +Allow issues to move within collections.