git-ents.gitmain
⌘K
foforge
lib.rs109 lines · 5.1 KB · rusthistorycomment on this file
1//! Anchor storage and projection: durable pointers into source — a blob,
2//! an optional line range, and a specific commit — and their read-time
3//! projection onto any other commit.
4//!
5//! This crate owns the `Anchor` abstraction from `docs/spec/anchor.sdoc`
6//! (overview.sdoc abstraction 3). Anchors resolve and project independently
7//! of any consumer: `ents-forge`'s `Comment` is merely the first client
8//! (its `anchor: RawTree` field embeds the tree an [`Anchor`] serializes
9//! to), and reviews, TODO trackers, and blame overlays can reuse the same
10//! mechanism. (`ents-forge` depends on this crate, not the other way
11//! around, so this crate's own examples and tests stand a `Comment`-shaped
12//! struct in for it rather than importing it.)
13//!
14//! # Spec coverage
15//!
16//! This crate implements, from `docs/spec/anchor.sdoc`:
17//!
18//! - `anchor.definition` — [`Anchor`] and [`capture`]'s validation.
19//! - `anchor.immutable` — no mutating API exists; [`snippet`] derives the
20//! anchored text at read time; the commit id is plain data.
21//! - `anchor.retention` — [`Anchor::content`] and [`Anchor::context`] are
22//! ordinary blob tree entries in the anchor's own serialized tree, never
23//! a gitlink.
24//! - `anchor.projection` — [`project`] / [`project_exact`] and the
25//! four-outcome [`Projection`] taxonomy.
26//! - `anchor.fuzzy-fallback` — [`project_from_context`], which [`project`]
27//! degrades to once the anchored commit has been garbage collected.
28//! - `anchor.working-tree` — [`capture_worktree`] (the on-disk bytes as a
29//! capture source, `HEAD` recorded as the best-effort commit field) and
30//! [`project_worktree`] (the on-disk bytes, or a caller-supplied buffer
31//! standing in for them, as a projection target).
32//!
33//! # Examples
34//!
35//! Capture an anchor, store it inside a `Comment`, read it back, and
36//! project it onto a later commit:
37//!
38//! ```
39//! use ents_anchor::{Anchor, LineRange, Projection};
40//! use facet_git_tree::RawTree;
41//!
42//! // Stands in for `ents-forge`'s `Comment` (this crate cannot
43//! // depend on `ents-forge`, which itself depends on this crate): any
44//! // struct embedding an anchor's tree by `RawTree` behaves identically.
45//! # #[derive(facet::Facet)]
46//! # struct Comment { body: String, anchor: RawTree }
47//! #
48//! # fn git(dir: &std::path::Path, args: &[&str]) {
49//! # let status = std::process::Command::new("git").arg("-C").arg(dir)
50//! # .args(["-c", "user.name=t", "-c", "user.email=t@example.com"])
51//! # .args(args).status().unwrap();
52//! # assert!(status.success());
53//! # }
54//! # let dir = tempfile::tempdir().expect("tempdir");
55//! # std::process::Command::new("git").arg("init").arg("-q").arg(dir.path()).status().unwrap();
56//! # std::fs::write(dir.path().join("file.txt"), (1..=10).map(|n| format!("line {n}\n")).collect::<String>()).unwrap();
57//! # git(dir.path(), &["add", "-A"]);
58//! # git(dir.path(), &["commit", "-q", "-m", "one"]);
59//! let repo = gix::open(dir.path()).expect("open");
60//!
61//! // Capture against HEAD: commit, path, blob, and range are validated
62//! // and recorded; content and context are embedded (`anchor.retention`).
63//! let anchor = ents_anchor::capture(&repo, "HEAD", "file.txt", Some(LineRange { start: 3, end: 4 }))
64//! .expect("capture");
65//! assert_eq!(ents_anchor::snippet(&anchor).expect("snippet"), "line 3\nline 4\n");
66//!
67//! // The anchor serializes into the same store the comment does; the
68//! // comment embeds it by tree id (`RawTree`), so the anchored content is
69//! // reachable from the comment's own ref.
70//! let store = facet_git_tree::ObjectStore::default();
71//! let anchor_tree = facet_git_tree::serialize_into(&anchor, &store).expect("serialize anchor");
72//! let comment = Comment {
73//! body: "these two lines look off by one".to_owned(),
74//! anchor: RawTree::new(anchor_tree),
75//! };
76//! let root = facet_git_tree::serialize_into(&comment, &store).expect("serialize comment");
77//!
78//! // Read the comment back and recover the identical anchor.
79//! let back: Comment = facet_git_tree::deserialize(&root, &store).expect("deserialize comment");
80//! let anchor_back: Anchor =
81//! facet_git_tree::deserialize(&back.anchor.oid(), &store).expect("deserialize anchor");
82//! assert_eq!(anchor_back, anchor);
83//!
84//! // Edit above the range and project: the anchor relocates, unmutated.
85//! # std::fs::write(dir.path().join("file.txt"), format!("added\n{}", (1..=10).map(|n| format!("line {n}\n")).collect::<String>())).unwrap();
86//! # git(dir.path(), &["add", "-A"]);
87//! # git(dir.path(), &["commit", "-q", "-m", "two"]);
88//! let repo = gix::open(dir.path()).expect("reopen");
89//! assert_eq!(
90//! ents_anchor::project(&repo, &anchor_back, "HEAD").expect("project"),
91//! Projection::Relocated {
92//! path: "file.txt".to_owned(),
93//! lines: Some(LineRange { start: 4, end: 5 }),
94//! }
95//! );
96//! ```
97
98mod anchor;
99mod binding;
100mod error;
101#[cfg(test)]
102mod fixture;
103mod projection;
104mod util;
105
106pub use anchor::{Anchor, LineRange, capture, capture_worktree, snippet};
107pub use binding::{Binding, EvalState, Validity, revalidate};
108pub use error::{Error, Result};
109pub use projection::{Projection, project, project_exact, project_from_context, project_worktree};