diff --git a/readme b/readme index 5faa3d0..828e2b3 100644 --- a/readme +++ b/readme @@ -56,12 +56,8 @@ A summary of commands and their functionality can be seen with: tsk help -tsk uses plain text files for all of its functionality. A workspace is a folder -that contains a .tsk/ directory created with the `tsk init` command. The -presence of a .tsk/ folder is searched recursively upwards until a filesystem -boundary or root is encountered. This means you can nest workspaces and use -folders to namespace tasks while also using tsk commands at any location within -a workspace. +tsk uses virtual text files tracked by special refs in a git repo for its +functionality. Any git repo can be made into a tsk workspace. New tasks are created with the `tsk push` command. A title is always required, but can be modified later. A unique identifier is selected automatically and a diff --git a/src/lib.rs b/src/lib.rs index 1fc4d9c..8394898 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,11 @@ pub mod errors; mod fzf; -mod namespace; mod merge; +mod namespace; mod object; mod patch; -mod propvalue; mod properties; +mod propvalue; mod queue; mod task; mod workspace; @@ -162,9 +162,7 @@ enum Commands { remote: Option, }, /// Push tsk refs to a git remote (default: origin). - GitPush { - remote: Option, - }, + GitPush { remote: Option }, /// Fetch tsk refs from a git remote (default: origin) and reconcile /// divergent task histories. Default strategy is merge; pass --rebase /// to replay local-only commits onto the remote tip instead. @@ -308,10 +306,14 @@ enum NamespaceAction { Current, /// Switch active namespace. With no name, fzf-picks from existing /// namespaces (plus a `` sentinel for creating one on the fly). - Switch { name: Option }, + Switch { + name: Option, + }, /// List every task bound in a namespace (defaults to active), /// regardless of which queue (if any) it's on. One row per id. - Tasks { name: Option }, + Tasks { + name: Option, + }, } #[derive(Subcommand)] @@ -326,7 +328,9 @@ enum QueueAction { }, /// Switch active queue. With no name, fzf-picks from existing queues /// (plus a `` sentinel for creating one on the fly). - Switch { name: Option }, + Switch { + name: Option, + }, } #[derive(Subcommand)] @@ -387,8 +391,7 @@ impl TaskId { let picked: Option = fzf::select(lines, ["--prompt=task> "])?; let picked = picked.ok_or(errors::Error::NoTasks)?; let id_str = picked.split('\t').next().unwrap_or(""); - let id: Id = - parse_id(id_str).map_err(|e| errors::Error::Parse(e.to_string()))?; + let id: Id = parse_id(id_str).map_err(|e| errors::Error::Parse(e.to_string()))?; Ok(TaskIdentifier::Id(id)) } } @@ -446,9 +449,7 @@ fn dispatch(cli: Cli) -> Result<()> { Commands::Swap => Workspace::from_path(dir)?.swap_top(), Commands::Rot => Workspace::from_path(dir)?.rot(), Commands::Tor => Workspace::from_path(dir)?.tor(), - Commands::Prioritize { task_id } => { - Workspace::from_path(dir)?.prioritize(task_id.into()) - } + Commands::Prioritize { task_id } => Workspace::from_path(dir)?.prioritize(task_id.into()), Commands::Deprioritize { task_id } => { Workspace::from_path(dir)?.deprioritize(task_id.into()) } @@ -723,9 +724,7 @@ fn pick_assign_target(ws: &Workspace) -> Result { .filter(|q| q != &cur) .collect(); if candidates.is_empty() { - return Err(errors::Error::Parse( - "No other queues to assign to".into(), - )); + return Err(errors::Error::Parse("No other queues to assign to".into())); } fzf::select::<_, String, _>(candidates, ["--prompt=assign to> "])? .ok_or_else(|| errors::Error::Parse("No queue selected".into())) @@ -926,11 +925,8 @@ fn command_prop(dir: PathBuf, action: PropAction) -> Result<()> { PropAction::Find { key, value } => { let key = match key { Some(k) => k, - None => fzf::select::<_, String, _>( - ws.property_keys()?, - ["--prompt=key> "], - )? - .ok_or_else(|| errors::Error::Parse("No key selected".into()))?, + None => fzf::select::<_, String, _>(ws.property_keys()?, ["--prompt=key> "])? + .ok_or_else(|| errors::Error::Parse("No key selected".into()))?, }; let value = match value { Some(v) if v == "" => None, @@ -938,11 +934,8 @@ fn command_prop(dir: PathBuf, action: PropAction) -> Result<()> { None => { let mut choices = ws.property_values(&key)?; choices.insert(0, "".to_string()); - let picked = fzf::select::<_, String, _>( - choices, - ["--prompt=value> "], - )? - .ok_or_else(|| errors::Error::Parse("No value selected".into()))?; + let picked = fzf::select::<_, String, _>(choices, ["--prompt=value> "])? + .ok_or_else(|| errors::Error::Parse("No value selected".into()))?; if picked == "" { None } else { @@ -1073,7 +1066,9 @@ fn picker_entries(existing: &[String], current: &str) -> Vec { } fn strip_picker_marker(s: &str) -> &str { - s.strip_prefix("* ").or_else(|| s.strip_prefix(" ")).unwrap_or(s) + s.strip_prefix("* ") + .or_else(|| s.strip_prefix(" ")) + .unwrap_or(s) } fn prompt_line(prompt: &str) -> Result { @@ -1103,10 +1098,7 @@ mod tests { #[test] fn picker_marks_current_and_appends_sentinel() { - let entries = picker_entries( - &["alpha".to_string(), "tsk".to_string()], - "tsk", - ); + let entries = picker_entries(&["alpha".to_string(), "tsk".to_string()], "tsk"); assert_eq!(entries, vec![" alpha", "* tsk", ""]); } diff --git a/src/merge.rs b/src/merge.rs index 1d04c03..68bc864 100644 --- a/src/merge.rs +++ b/src/merge.rs @@ -88,7 +88,10 @@ pub fn reconcile_task_refs( } for r in repo.references_glob(&format!("{fetched_tasks}*"))? { let r = r?; - if let Some(name) = r.name().and_then(|n| n.strip_prefix(fetched_tasks.as_str())) { + if let Some(name) = r + .name() + .and_then(|n| n.strip_prefix(fetched_tasks.as_str())) + { stables.insert(name.to_string()); } } @@ -330,14 +333,8 @@ fn reconcile_namespace_one( format!("rebase-bind {name}") }; let parents: Vec<&Commit> = vec![&local_commit, &remote_commit]; - let new_oid = repo.commit( - None, - &sig, - &sig, - &msg, - &repo.find_tree(tree_oid)?, - &parents, - )?; + let new_oid = + repo.commit(None, &sig, &sig, &msg, &repo.find_tree(tree_oid)?, &parents)?; repo.reference(&namespace::refname(name), new_oid, true, &msg)?; Ok(Some(NamespaceReconciliation { namespace: name.to_string(), @@ -369,10 +366,7 @@ pub struct QueueReconciliation { /// /// `can_pull`: 3-way bool. Local change wins if it differs from base; /// otherwise take remote. -pub fn reconcile_queue_refs( - repo: &Repository, - remote: &str, -) -> Result> { +pub fn reconcile_queue_refs(repo: &Repository, remote: &str) -> Result> { let fetched = format!("{}queues/", fetched_prefix(remote)); let mut names: BTreeSet = BTreeSet::new(); for r in repo.references_glob(&format!("{QUEUE_REF_PREFIX}*"))? { @@ -448,7 +442,9 @@ fn reconcile_queue_one( &parents, )?; repo.reference(&queue::refname(name), new_oid, true, "merge")?; - Ok(Some(QueueReconciliation { name: name.to_string() })) + Ok(Some(QueueReconciliation { + name: name.to_string(), + })) } } } @@ -468,7 +464,11 @@ fn three_way_queue_merge(base: &Queue, local: &Queue, remote: &Queue) -> Queue { let in_remote = remote_set.contains(*s); // present in base → kept iff neither side removed it. // not in base → added by either side, keep. - if in_base { in_local && in_remote } else { in_local || in_remote } + if in_base { + in_local && in_remote + } else { + in_local || in_remote + } }) .collect(); @@ -510,7 +510,11 @@ fn three_way_queue_merge(base: &Queue, local: &Queue, remote: &Queue) -> Queue { local.can_pull }; - Queue { index, can_pull, inbox } + Queue { + index, + can_pull, + inbox, + } } /// After task refs are reconciled, copy every other fetched ref @@ -748,7 +752,8 @@ mod test { let content_oid = repo.blob(b"v0").unwrap(); let mut tb = repo.treebuilder(None).unwrap(); tb.insert("content", content_oid, 0o100644).unwrap(); - tb.insert("title", repo.blob(b"v0").unwrap(), 0o100644).unwrap(); + tb.insert("title", repo.blob(b"v0").unwrap(), 0o100644) + .unwrap(); tb.insert("status", repo.blob(b"open\n").unwrap(), 0o100644) .unwrap(); let tree_oid = tb.write().unwrap(); diff --git a/src/namespace.rs b/src/namespace.rs index 7165e2a..ffe289e 100644 --- a/src/namespace.rs +++ b/src/namespace.rs @@ -136,12 +136,7 @@ pub fn write(repo: &Repository, name: &str, ns: &Namespace, message: &str) -> Re /// Allocate the next human id, insert the binding, and persist. Returns the /// human id assigned. -pub fn assign_id( - repo: &Repository, - name: &str, - stable: StableId, - message: &str, -) -> Result { +pub fn assign_id(repo: &Repository, name: &str, stable: StableId, message: &str) -> Result { let mut ns = read(repo, name)?; let human = ns.next; ns.next += 1; @@ -178,12 +173,7 @@ pub fn lookup(repo: &Repository, name: &str, human: u32) -> Result Result { +pub fn ensure_bound(repo: &Repository, name: &str, stable: StableId, message: &str) -> Result { match human_for(repo, name, &stable)? { Some(h) => Ok(h), None => assign_id(repo, name, stable, message), diff --git a/src/object.rs b/src/object.rs index 99dbf4f..ed169ff 100644 --- a/src/object.rs +++ b/src/object.rs @@ -99,14 +99,7 @@ pub fn create(repo: &Repository, task: &Task, message: &str) -> Result let stable = StableId(content_oid.to_string()); let tree_oid = build_tree(repo, content_oid, &task.properties)?; let sig = signature(repo); - let commit = repo.commit( - None, - &sig, - &sig, - message, - &repo.find_tree(tree_oid)?, - &[], - )?; + let commit = repo.commit(None, &sig, &sig, message, &repo.find_tree(tree_oid)?, &[])?; repo.reference(&stable.refname(), commit, true, message)?; Ok(stable) } @@ -207,8 +200,7 @@ mod test { let dir = tempfile::tempdir().unwrap(); let repo = init_repo(dir.path()); let mut t = Task::new("Hello\n\nbody text"); - t.properties - .insert("priority".into(), vec!["high".into()]); + t.properties.insert("priority".into(), vec!["high".into()]); t.properties .insert("tag".into(), vec!["alpha".into(), "beta".into()]); let id = create(&repo, &t, "create").unwrap(); @@ -229,7 +221,11 @@ mod test { t2.content = "v2".into(); update(&repo, &id, &t2, "edit").unwrap(); // Two commits in the chain. - let head = repo.find_reference(&id.refname()).unwrap().target().unwrap(); + let head = repo + .find_reference(&id.refname()) + .unwrap() + .target() + .unwrap(); let head_commit = repo.find_commit(head).unwrap(); assert_eq!(head_commit.parent_count(), 1); let read_back = read(&repo, &id).unwrap().unwrap(); @@ -242,9 +238,17 @@ mod test { let repo = init_repo(dir.path()); let t = Task::new("same"); let id = create(&repo, &t, "create").unwrap(); - let head1 = repo.find_reference(&id.refname()).unwrap().target().unwrap(); + let head1 = repo + .find_reference(&id.refname()) + .unwrap() + .target() + .unwrap(); update(&repo, &id, &t, "noop").unwrap(); - let head2 = repo.find_reference(&id.refname()).unwrap().target().unwrap(); + let head2 = repo + .find_reference(&id.refname()) + .unwrap() + .target() + .unwrap(); assert_eq!(head1, head2); } diff --git a/src/patch.rs b/src/patch.rs index 44d489f..af171d7 100644 --- a/src/patch.rs +++ b/src/patch.rs @@ -57,13 +57,11 @@ pub struct ExportOpts { pub bind: Option<(String, u32)>, } -pub fn export_task( - repo: &Repository, - stable: &StableId, - opts: &ExportOpts, -) -> Result { +pub fn export_task(repo: &Repository, stable: &StableId, opts: &ExportOpts) -> Result { let r = repo.find_reference(&stable.refname())?; - let tip = r.target().ok_or_else(|| Error::Parse("task ref empty".into()))?; + let tip = r + .target() + .ok_or_else(|| Error::Parse("task ref empty".into()))?; // Collect root → tip. let mut chain: Vec = Vec::new(); let mut cur = Some(repo.find_commit(tip)?); @@ -139,7 +137,9 @@ fn write_entry( writeln!( out, "X-Tsk-Parent: {}", - parent.map(|o| o.to_string()).unwrap_or_else(|| "none".into()) + parent + .map(|o| o.to_string()) + .unwrap_or_else(|| "none".into()) ) .unwrap(); if let Some((ns, human)) = bind { @@ -163,8 +163,7 @@ fn write_entry( } let blob = entry.to_object(repo)?.peel_to_blob()?; let bytes = blob.content(); - let as_str = - std::str::from_utf8(bytes).map_err(|e| Error::Parse(e.to_string()))?; + let as_str = std::str::from_utf8(bytes).map_err(|e| Error::Parse(e.to_string()))?; let mangled = mangle_from(as_str); writeln!(out, "file: {name}").unwrap(); writeln!(out, "size: {}", mangled.len()).unwrap(); @@ -286,7 +285,10 @@ fn import_one_chain(repo: &Repository, entries: &[Entry]) -> Result = prev.into_iter().map(|o| repo.find_commit(o).unwrap()).collect(); + let parents: Vec = prev + .into_iter() + .map(|o| repo.find_commit(o).unwrap()) + .collect(); let parent_refs: Vec<&git2::Commit> = parents.iter().collect(); let commit_oid = repo.commit( None, @@ -438,8 +440,8 @@ fn parse_entry(chunk: &str) -> Result { if rest.len() < size + 1 { return Err(Error::Parse("truncated file body".into())); } - let mangled = std::str::from_utf8(&rest[..size]) - .map_err(|e| Error::Parse(e.to_string()))?; + let mangled = + std::str::from_utf8(&rest[..size]).map_err(|e| Error::Parse(e.to_string()))?; if rest[size] != b'\n' { return Err(Error::Parse("missing newline after file body".into())); } @@ -530,8 +532,7 @@ mod test { fn tamper_detected_via_stable_id_check() { let dir = tempfile::tempdir().unwrap(); let src = init_repo(dir.path()); - let stable = - object::create(&src, &Task::new("original content"), "create").unwrap(); + let stable = object::create(&src, &Task::new("original content"), "create").unwrap(); let mbox = export_task(&src, &stable, &ExportOpts { bind: None }).unwrap(); // Flip the content body without updating the stable id header. // Equal-length substitution so size-prefix parsing still aligns; only @@ -554,8 +555,7 @@ mod test { let s = "preamble\nFrom the desk of...\n>From me\nbody\n"; let mangled = mangle_from(s); assert_eq!( - mangled, - "preamble\n>From the desk of...\n>>From me\nbody\n", + mangled, "preamble\n>From the desk of...\n>>From me\nbody\n", "every ^>*From line gets one extra '>'", ); assert_eq!(unmangle_from(&mangled), s); @@ -607,8 +607,7 @@ mod test { // Alice creates → exports. Bob imports. let alice_dir = tempfile::tempdir().unwrap(); let alice_repo = init_repo_as(alice_dir.path(), "Alice", "a@x"); - let stable = - object::create(&alice_repo, &Task::new("from alice"), "create").unwrap(); + let stable = object::create(&alice_repo, &Task::new("from alice"), "create").unwrap(); let mbox = export_task(&alice_repo, &stable, &ExportOpts { bind: None }).unwrap(); let bob_dir = tempfile::tempdir().unwrap(); @@ -631,10 +630,8 @@ mod test { // second commit authored by Bob. let alice_dir = tempfile::tempdir().unwrap(); let alice_repo = init_repo_as(alice_dir.path(), "Alice", "a@x"); - let stable = - object::create(&alice_repo, &Task::new("from alice"), "create").unwrap(); - let alice_mbox = - export_task(&alice_repo, &stable, &ExportOpts { bind: None }).unwrap(); + let stable = object::create(&alice_repo, &Task::new("from alice"), "create").unwrap(); + let alice_mbox = export_task(&alice_repo, &stable, &ExportOpts { bind: None }).unwrap(); let bob_dir = tempfile::tempdir().unwrap(); let bob_repo = init_repo_as(bob_dir.path(), "Bob", "b@x"); diff --git a/src/properties.rs b/src/properties.rs index b737297..96d706a 100644 --- a/src/properties.rs +++ b/src/properties.rs @@ -181,7 +181,14 @@ mod test { let s1 = object::create(&repo, &object::Task::new("a"), "c").unwrap(); let s2 = object::create(&repo, &object::Task::new("b"), "c").unwrap(); set(&repo, "priority", &s1, &["high".into()], "x").unwrap(); - set(&repo, "priority", &s2, &["low".into(), "medium".into()], "x").unwrap(); + set( + &repo, + "priority", + &s2, + &["low".into(), "medium".into()], + "x", + ) + .unwrap(); let high = find(&repo, "priority", Some("high")).unwrap(); assert_eq!(high, vec![s1.clone()]); let any_priority = find(&repo, "priority", None).unwrap(); diff --git a/src/queue.rs b/src/queue.rs index 0b9f9d9..6046509 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -58,7 +58,11 @@ impl Queue { } pub fn read(repo: &Repository, name: &str) -> Result { - let Some(target) = repo.find_reference(&refname(name)).ok().and_then(|r| r.target()) else { + let Some(target) = repo + .find_reference(&refname(name)) + .ok() + .and_then(|r| r.target()) + else { return Ok(Queue::new(name)); }; read_at_commit(repo, name, target) @@ -97,11 +101,7 @@ pub fn read_at_commit(repo: &Repository, name: &str, commit_oid: Oid) -> Result< pub fn build_tree(repo: &Repository, q: &Queue) -> Result { let mut tb = repo.treebuilder(None)?; - let index_text: String = q - .index - .iter() - .map(|s| format!("{}\n", s.0)) - .collect(); + let index_text: String = q.index.iter().map(|s| format!("{}\n", s.0)).collect(); let index_oid = repo.blob(index_text.as_bytes())?; tb.insert(INDEX_FILE, index_oid, 0o100644)?; let cp = if q.can_pull { "true\n" } else { "false\n" }; @@ -169,12 +169,7 @@ pub fn push_top(repo: &Repository, name: &str, stable: StableId, message: &str) write(repo, name, &q, message) } -pub fn push_bottom( - repo: &Repository, - name: &str, - stable: StableId, - message: &str, -) -> Result<()> { +pub fn push_bottom(repo: &Repository, name: &str, stable: StableId, message: &str) -> Result<()> { let mut q = read(repo, name)?; q.index.retain(|s| s != &stable); q.index.push(stable); diff --git a/src/workspace.rs b/src/workspace.rs index 0aa0693..ea59bab 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -333,8 +333,7 @@ impl Workspace { .insert(STATUS_KEY.into(), vec![STATUS_OPEN.into()]); let stable = object::create(&repo, &obj, "create")?; properties::reindex_task(&repo, &stable, &obj.properties)?; - let human = - namespace::assign_id(&repo, &active_ns, stable.clone(), "assign-id")?; + let human = namespace::assign_id(&repo, &active_ns, stable.clone(), "assign-id")?; return Ok(Self::make_task(Id(human), stable, obj)); } @@ -480,7 +479,9 @@ impl Workspace { let by_stable = ns_reverse(&namespace::read(&repo, &self.namespace())?); let mut out = Vec::new(); for stable in properties::find(&repo, key, value)? { - let Some(&human) = by_stable.get(&stable) else { continue }; + let Some(&human) = by_stable.get(&stable) else { + continue; + }; out.push((Id(human), stable.clone(), Self::title_for(&repo, &stable)?)); } Ok(out) @@ -500,9 +501,15 @@ impl Workspace { let mut out = Vec::new(); for stable in queue::read(&repo, &self.queue())?.index { // Skip tasks not visible in the active namespace (different ns owns them). - let Some(&human) = by_stable.get(&stable) else { continue }; + let Some(&human) = by_stable.get(&stable) else { + continue; + }; let title = Self::title_for(&repo, &stable)?; - out.push(StackEntry { id: Id(human), stable, title }); + out.push(StackEntry { + id: Id(human), + stable, + title, + }); } Ok(out) } @@ -514,7 +521,11 @@ impl Workspace { let mut out = Vec::new(); for (human, stable) in namespace::read(&repo, name)?.mapping { let title = Self::title_for(&repo, &stable)?; - out.push(StackEntry { id: Id(human), stable, title }); + out.push(StackEntry { + id: Id(human), + stable, + title, + }); } Ok(out) } @@ -557,11 +568,7 @@ impl Workspace { /// Export multiple tasks as a single concatenated mbox stream. /// Each task's full commit chain is emitted in order; the importer /// groups them back by stable id. - pub fn export_tasks( - &self, - identifiers: &[TaskIdentifier], - bind: bool, - ) -> Result { + pub fn export_tasks(&self, identifiers: &[TaskIdentifier], bind: bool) -> Result { let repo = self.repo()?; let mut out = String::new(); for ident in identifiers { @@ -726,7 +733,12 @@ impl Workspace { } } - Ok((queues_pruned, prop_orphans, ghost_bindings, orphan_queue_entries)) + Ok(( + queues_pruned, + prop_orphans, + ghost_bindings, + orphan_queue_entries, + )) } /// Flip a task back to `status=open` and push it to the top of the @@ -811,7 +823,11 @@ impl Workspace { idx.push(stable); } }, - if to_front { "prioritize" } else { "deprioritize" }, + if to_front { + "prioritize" + } else { + "deprioritize" + }, ) } @@ -886,7 +902,12 @@ impl Workspace { .map(|(s, _)| s.to_string()) .unwrap_or_else(|| key.clone()); let title = Self::title_for(&repo, &stable)?; - out.push(InboxItem { key, source_queue, stable, title }); + out.push(InboxItem { + key, + source_queue, + stable, + title, + }); } Ok(out) } @@ -1063,10 +1084,7 @@ impl Workspace { /// Refs to push after `reject_inbox`: the active queue (entry left the /// inbox) and the source queue (entry was bounced back into its inbox). pub fn refs_for_reject_inbox(&self, source_queue: &str) -> Vec { - vec![ - queue::refname(&self.queue()), - queue::refname(source_queue), - ] + vec![queue::refname(&self.queue()), queue::refname(source_queue)] } /// Refs to fetch before listing the inbox: just the active queue. @@ -1102,7 +1120,11 @@ impl Workspace { let namespaces = merge::reconcile_namespace_refs(&repo, remote)?; let queues = merge::reconcile_queue_refs(&repo, remote)?; merge::fast_forward_non_task_refs(&repo, remote)?; - Ok(merge::PullOutcome { tasks, namespaces, queues }) + Ok(merge::PullOutcome { + tasks, + namespaces, + queues, + }) } } @@ -1261,7 +1283,10 @@ mod test { ws.switch_queue("review").unwrap(); ws.reject_inbox(&assign_key).unwrap(); let inbox_here = ws.list_inbox().unwrap(); - assert!(inbox_here.is_empty(), "rejected item must leave receiver inbox"); + assert!( + inbox_here.is_empty(), + "rejected item must leave receiver inbox" + ); ws.switch_queue("tsk").unwrap(); let returned = ws.list_inbox().unwrap(); assert_eq!(returned.len(), 1, "rejected item must land in sender inbox"); @@ -1281,7 +1306,9 @@ mod test { let r = ws.pull_from_queue("private", TaskIdentifier::Id(id)); assert!(r.is_err(), "pull from can-pull=false queue must fail"); ws.create_queue("private", Some(true)).unwrap(); - let pulled = ws.pull_from_queue("private", TaskIdentifier::Id(id)).unwrap(); + let pulled = ws + .pull_from_queue("private", TaskIdentifier::Id(id)) + .unwrap(); assert_eq!(pulled.0, id.0); let stack = ws.read_stack().unwrap(); assert_eq!(stack.len(), 1); @@ -1407,9 +1434,7 @@ mod test { ws.push_task(t).unwrap(); // Index reflects the new open task. - let opens = ws - .find_by_property(STATUS_KEY, Some(STATUS_OPEN)) - .unwrap(); + let opens = ws.find_by_property(STATUS_KEY, Some(STATUS_OPEN)).unwrap(); assert_eq!(opens.len(), 1); assert_eq!(opens[0].0, id); @@ -1422,9 +1447,7 @@ mod test { Some(&vec![STATUS_DONE.to_string()]) ); assert!(ws.read_stack().unwrap().is_empty()); - let dones = ws - .find_by_property(STATUS_KEY, Some(STATUS_DONE)) - .unwrap(); + let dones = ws.find_by_property(STATUS_KEY, Some(STATUS_DONE)).unwrap(); assert_eq!(dones.len(), 1); assert_eq!(dones[0].0, id); } @@ -1516,7 +1539,10 @@ mod test { namespace::unassign_id(&repo, "tsk", id.0, "test-unbind").unwrap(); // Same content again should re-bind into active ns with a new id. let again = ws.new_task("legacy task".into(), "".into()).unwrap(); - assert_eq!(again.stable, stable, "stable id must match the orphaned ref"); + assert_eq!( + again.stable, stable, + "stable id must match the orphaned ref" + ); assert_ne!( again.id, id, "rebinding allocates a fresh human id from `next`" @@ -1549,7 +1575,11 @@ mod test { assert_eq!(qe, 1, "orphan queue index entry dropped"); assert!(repo.find_reference(&queue::refname("empty")).is_err()); assert!(repo.find_reference(&properties::refname("ghost")).is_err()); - assert!(namespace::human_for(&repo, "tsk", &orphan).unwrap().is_none()); + assert!( + namespace::human_for(&repo, "tsk", &orphan) + .unwrap() + .is_none() + ); // Idempotent: second pass changes nothing. assert_eq!(ws.gc_refs().unwrap(), (0, 0, 0, 0)); diff --git a/tests/multi_user.rs b/tests/multi_user.rs index c6eca70..19862d4 100644 --- a/tests/multi_user.rs +++ b/tests/multi_user.rs @@ -48,7 +48,12 @@ fn tsk_ok(dir: &Path, args: &[&str]) -> String { fn make_clone(origin: &Path, dest: &Path, name: &str, email: &str) { let _ = Command::new("git") - .args(["clone", "-q", origin.to_str().unwrap(), dest.to_str().unwrap()]) + .args([ + "clone", + "-q", + origin.to_str().unwrap(), + dest.to_str().unwrap(), + ]) .status() .expect("git clone"); git(dest, &["config", "user.name", name]); @@ -111,7 +116,10 @@ fn assign_to_other_queue_visible_after_push_pull() { tsk_ok(&alice, &["git-pull"]); tsk_ok(&alice, &["push", "needs review"]); let assign_out = tsk_ok(&alice, &["assign", "review", "-R", ""]); - assert!(assign_out.contains("Assigned to review"), "got {assign_out}"); + assert!( + assign_out.contains("Assigned to review"), + "got {assign_out}" + ); tsk_ok(&alice, &["git-push"]); // Bob switches to review, pulls, sees inbox. @@ -193,7 +201,10 @@ fn property_set_find_round_trip_via_binary() { assert!(list.contains("tag\tbeta"), "beta survives: {list}"); // Replace whole property. - tsk_ok(&alice, &["prop", "set", "-T", "tsk-1", "priority", "medium"]); + tsk_ok( + &alice, + &["prop", "set", "-T", "tsk-1", "priority", "medium"], + ); let list = tsk_ok(&alice, &["prop", "list", "-T", "tsk-1"]); assert!(list.contains("priority\tmedium"), "got {list}"); assert!(!list.contains("priority\thigh"), "got {list}"); @@ -285,7 +296,10 @@ fn divergent_task_edits_merge_on_pull() { // the task. let listing = tsk_ok(&bob, &["prop", "list", "-T", "tsk-1"]); eprintln!("LISTING: {listing}"); - assert!(listing.contains("priority\thigh"), "alice's edit lost: {listing}"); + assert!( + listing.contains("priority\thigh"), + "alice's edit lost: {listing}" + ); assert!(listing.contains("owner\tbob"), "bob's edit lost: {listing}"); } @@ -309,7 +323,10 @@ fn divergent_task_edits_rebase_on_pull() { // Both edits survived. let listing = tsk_ok(&bob, &["prop", "list", "-T", "tsk-1"]); - assert!(listing.contains("priority\thigh"), "alice's edit lost: {listing}"); + assert!( + listing.contains("priority\thigh"), + "alice's edit lost: {listing}" + ); assert!(listing.contains("owner\tbob"), "bob's edit lost: {listing}"); }