pub struct Repo { /* private fields */ }Expand description
A discovered git repository.
Implementations§
Source§impl Repo
impl Repo
Sourcepub fn discover(path: &Path) -> Result<Self, GitError>
pub fn discover(path: &Path) -> Result<Self, GitError>
Discover the repository containing path (walking upwards to the .git).
§Errors
Returns GitError::Git if no repository is found or it cannot be opened.
Sourcepub fn common_dir(&self) -> &Path
pub fn common_dir(&self) -> &Path
The repository’s common git directory. The cache lives under here so it is shared across linked worktrees (which each have their own git dir).
Sourcepub fn git_dir(&self) -> &Path
pub fn git_dir(&self) -> &Path
This worktree’s git directory (per-worktree; the graph DB lives here).
Sourcepub fn hooks_dir(&self) -> PathBuf
pub fn hooks_dir(&self) -> PathBuf
The directory git actually looks in for hooks. Honours core.hooksPath
(absolute, or relative to the working-tree root — else the git dir); when
unset it is <common git dir>/hooks, so managed hooks are shared across
linked worktrees. roteiro init installs into this so its hooks run
wherever git expects them.
Sourcepub fn workdir(&self) -> Option<&Path>
pub fn workdir(&self) -> Option<&Path>
The working directory, if this is not a bare repository. The dirty overlay reads uncommitted file contents from here.
Sourcepub fn blob_oid(&self, bytes: &[u8]) -> Result<String, GitError>
pub fn blob_oid(&self, bytes: &[u8]) -> Result<String, GitError>
The hex git blob object id that bytes would have, without writing
anything. Used to detect whether a working-copy file differs from the
committed blob (same content ⇒ same id).
§Errors
Returns GitError::Git if hashing fails.
Sourcepub fn head_tree_id(&self) -> Result<String, GitError>
pub fn head_tree_id(&self) -> Result<String, GitError>
Hex object id of the tree at HEAD.
§Errors
Returns GitError::Git if HEAD cannot be resolved to a tree.
Sourcepub fn head_commit_id(&self) -> Result<String, GitError>
pub fn head_commit_id(&self) -> Result<String, GitError>
Hex object id of the commit at HEAD — a stable permalink ref for the tree
the graph was built from (used to build source links).
§Errors
Returns GitError::Git if HEAD cannot be resolved to a commit.
Sourcepub fn head_commit_time(&self) -> Result<i64, GitError>
pub fn head_commit_time(&self) -> Result<i64, GitError>
Seconds since the Unix epoch of the HEAD commit’s commit time, in UTC.
Added for analyzer-asset provisioning: an advisory database that is a git
checkout has no publication date of its own, and cargo audit reports
none at all when it is pointed at a database with --db rather than
resolving one itself. The commit time is the publication date, and it is
what lets a result be labelled possibly stale with a number attached
(ADR-0012).
§Errors
Returns GitError::Git if HEAD cannot be resolved to a commit or the
commit carries no readable time.
Who last changed each of paths, and when — the equivalent of
git log -1 --format='%an %ct' -- <path> for a whole set at once.
Used to attribute the authored layer to a person, which is what puts a
concept in OKF’s human-reviewed trust tier (§5.3) rather than the
machine-confirmed one. The human: prefix is applied by the renderer, not
here — this returns the bare identity.
§Per path, because the claim is per document
The obvious cheap answer is the HEAD commit’s author, and it is wrong in
a way the format cannot survive: verified: [{ by: human:<id> }] asserts
that that person stands behind that document, so attributing the whole
repository to whoever pushed last records a confirmation nobody made. A
bot merge at HEAD would mark every ADR as human-reviewed by the bot.
§What “last changed” means here
Walking newest-first by commit time, a commit changed a path when the blob
at that path differs from the blob in every parent — the same
definition git log -- <path> uses, so a merge that only carried a change
across is not credited with making it. A path present in a root commit was
changed by that commit.
A path absent from the result was never resolved: the history ran out first (a shallow clone), a commit’s parents could not be read (a partial clone), or the walk failed. The caller must read that as no confirmation, never as the tool’s — substituting a machine actor would move a concept down a trust tier silently, which is worse than claiming nothing.
Every commit this cannot fully compare is skipped rather than guessed at, for the same reason: the only wrong answer that costs anything here is a confident one.
§Errors
Returns GitError::Git if HEAD cannot be resolved or the history
cannot be walked.
Sourcepub fn origin_url(&self) -> Option<String>
pub fn origin_url(&self) -> Option<String>
The origin remote’s fetch URL, if one is configured — e.g. to derive a
web “blob” base for source links. None when there is no origin remote.
Sourcepub fn blobs_at(&self, rev: &str) -> Result<Vec<BlobRef>, GitError>
pub fn blobs_at(&self, rev: &str) -> Result<Vec<BlobRef>, GitError>
Every blob reachable from an arbitrary commit-or-tree rev (a hex oid),
with full paths — like Repo::walk_blobs but for any point in history,
not just HEAD. A commit oid is peeled to its tree, so a submodule pin (a
commit sha) works directly. The primitive for extracting a repo’s graph at
the version a spoke pins (ADR-0009 step 8 — version-pin resolution).
§Errors
Returns GitError if rev cannot be resolved to a tree, the tree cannot
be traversed, or a path is not valid UTF-8.
Sourcepub fn submodules(&self) -> Result<Vec<Submodule>, GitError>
pub fn submodules(&self) -> Result<Vec<Submodule>, GitError>
Every git submodule pinned in the HEAD tree, sorted by path: a gitlink
(commit) entry gives the path and the commit it points at, enriched with
its .gitmodules URL when declared. The pinned commit is the version a
deployment repo ships (ADR-0009 derived facts). Empty when there are none.
§Errors
Returns GitError if the tree cannot be traversed, .gitmodules cannot
be read, or a path is not valid UTF-8.
Sourcepub fn submodules_at(&self, rev: &str) -> Result<Vec<Submodule>, GitError>
pub fn submodules_at(&self, rev: &str) -> Result<Vec<Submodule>, GitError>
Every git submodule pinned at an arbitrary commit/tree rev, sorted by path
— like Repo::submodules but for a historical point, so a hub graph
extracted at a pinned version (ADR-0009 step 8) carries its own submodules
as they were then.
§Errors
As Repo::submodules, plus if rev cannot be resolved to a tree.
Sourcepub fn index_submodules(&self) -> Result<Vec<Submodule>, GitError>
pub fn index_submodules(&self) -> Result<Vec<Submodule>, GitError>
Every git submodule pinned in the staged index (the tree a commit would
record), sorted by path. Same shape as Repo::submodules but reads the
gitlinks (and .gitmodules) from the index, so the index-aware sync — the
pre-commit gate — reflects a staged submodule bump, not the HEAD pin.
§Errors
As Repo::submodules, plus index-load failure.
Sourcepub fn resolve_base(&self, spec: &str) -> Result<BaseResolution, GitError>
pub fn resolve_base(&self, spec: &str) -> Result<BaseResolution, GitError>
What a --base <spec> actually bound to — the ref, the commit, and how
that commit stands against its upstream (issue #649).
§The defect this exists to make visible
Repo::changed_between resolves its base with rev_parse_single, so the
bare name main binds to the local branch, never to
refs/remotes/origin/main. That is correct for rev_parse_single and it is
what git itself does; the problem was that nothing surfaced the consequence.
A local main seventeen commits behind its upstream answers a different
question from the one that was asked, and answers it in output textually
identical to a correct run: review --base main reported 33 changed files
where the real footprint was 2, with drift: [] and exit 0.
Rebasing does not save you, which is what made it durable — rebasing the
branch does not move the local main ref.
§Why a superset is the silent case and divergence is the dangerous one
When the base is an ancestor of its upstream (Upstream::ahead is 0),
the diff is a strict superset of the true one: more files, all of yours
included. Every drift item that would have been found is still found, so the
gate still holds and the run looks more thorough rather than less. Nothing
fails, which is why it went unnoticed for a whole session.
When the two have diverged (Upstream::ahead and Upstream::behind
are both non-zero), the diff can omit changes that exist on both sides of
the fork, and then a clean verdict is worthless rather than merely wide.
Upstream::diverged separates the two so a caller can say different
things about them.
§Errors
Returns GitError::Git if spec cannot be resolved to a single commit,
or if a reachability walk against the upstream fails.
Sourcepub fn changed_between(&self, base: &str) -> Result<Vec<ChangedFile>, GitError>
pub fn changed_between(&self, base: &str) -> Result<Vec<ChangedFile>, GitError>
The tracked files that differ between base (any revspec — a branch,
HEAD~3, a sha) and the current HEAD, sorted by path. Used for
change-scoped tooling over a commit range (e.g. roteiro review --base main), distinct from Repo::changed_files, which compares the working
tree to HEAD. A path only in HEAD is added, only in base is deleted.
Callers that report what they compared against should resolve the spec
once with Repo::resolve_base and pass BaseResolution::commit here,
so the commit named in the report and the commit actually diffed cannot be
two different answers to one question.
§Errors
Returns GitError if base cannot be resolved to a tree, a tree cannot
be traversed, or a path is not valid UTF-8.
Sourcepub fn read_blob(&self, oid: &str) -> Result<Vec<u8>, GitError>
pub fn read_blob(&self, oid: &str) -> Result<Vec<u8>, GitError>
Read the bytes of the blob with hex object id oid.
§Errors
Returns GitError::Git if the id is malformed or the object is absent.
Sourcepub fn read_source(
&self,
blob: &BlobRef,
source: GraphSource,
) -> Result<Option<Vec<u8>>, GitError>
pub fn read_source( &self, blob: &BlobRef, source: GraphSource, ) -> Result<Option<Vec<u8>>, GitError>
The bytes of a tracked file’s authored source, from the tree named by
source: the committed HEAD blob, the staged blob, or the file as it
stands on disk (unstaged edits included, and not the git index).
The Worktree reading matches crate::sync_worktree, which the derived
graph is built from, so the authored and derived layers stay consistent —
see GraphSource for why that pairing is one type rather than two
independent choices.
Returns Ok(None) when a worktree file has been deleted, so the caller
drops it.
§Errors
Returns GitError::Git if the blob cannot be read, or if reading the
working-tree copy fails for any reason other than the file being absent.
Sourcepub fn changed_files(&self) -> Result<Vec<ChangedFile>, GitError>
pub fn changed_files(&self) -> Result<Vec<ChangedFile>, GitError>
Tracked files whose working-tree content differs from HEAD — the change
about to be committed. A file is changed when its working-copy bytes hash
to a different blob id than the committed one (content, not mtime), and
deleted when it is absent from the working tree. Untracked new files are
not reported (they are not in the HEAD tree). Same detection as
crate::sync_worktree, surfaced for change-scoped tooling.
§Errors
Returns GitError on a git failure. In a bare repo (no working tree)
the change set is empty.
Sourcepub fn index_files(&self) -> Result<Vec<BlobRef>, GitError>
pub fn index_files(&self) -> Result<Vec<BlobRef>, GitError>
The staged files: each regular blob in the git index with its staged
object id, sorted by path. This is the tree that a commit would record —
unlike Repo::changed_files (the working tree) — so it lets tooling gate
exactly what is about to be committed (the pre-commit index-aware check).
Conflict (unmerged) entries, directories, submodules and symlinks are
skipped.
§Errors
Returns GitError if the index cannot be loaded or a path is not valid
UTF-8.
Sourcepub fn added_since_head(
&self,
head_paths: &BTreeSet<&str>,
) -> Result<BTreeSet<String>, GitError>
pub fn added_since_head( &self, head_paths: &BTreeSet<&str>, ) -> Result<BTreeSet<String>, GitError>
Every path the working tree has that head_paths does not — the files
a commit would add, whether or not they have been staged yet.
§Why this exists rather than untracked_files alone
The obvious spelling of “new files in the working tree” is
Repo::untracked_files, and it is wrong in a way that reads as
correct. The two sets classify against different trees:
untracked_files is defined against the index, and a caller’s
head_paths comes from HEAD. So git add on a new file removes it
from the untracked set without adding it to HEAD, and the union of the
two has a hole exactly the size of “staged, not yet committed”.
That hole has been found three times, in three surfaces, each time as a silent wrong answer rather than a failure:
- issue #636 —
syncdeleted a node from the graph ongit add; - issue #649 —
reviewsaid “no working-tree changes to review” on a tree with a staged addition in it; - issue #657 —
checkreported 0 violations on drift it had caught onegit addearlier, silencing the gate at the moment it matters most.
Each was fixed where it was found, which left three copies of one rule. This is the rule, once, so the fourth surface inherits it instead of re-deriving it.
head_paths is a parameter rather than something walked here because
every caller already holds HEAD’s paths for its own reasons; walking the
tree again to re-derive them would make the shared version cost more than
the copies it replaces.
.gitignore is honoured, and the union states how: an ignored file is
absent from the dirwalk, so it enters only by being in the index — which
takes a deliberate git add -f. That is the right outcome rather than a
leak, because force-adding overrides the ignore and the file will be
committed regardless.
§Errors
Returns GitError if the dirwalk or the index cannot be read.
Sourcepub fn untracked_files(&self) -> Result<Vec<String>, GitError>
pub fn untracked_files(&self) -> Result<Vec<String>, GitError>
Untracked, non-ignored regular files in the working tree: everything the dirwalk finds that the index does not carry.
Not “files in neither HEAD nor the index”, which this said until #662
pointed at the contradiction with Repo::added_since_head directly
above. The set is defined against the index alone, so a path can be in
HEAD and in here at once: git rm --cached f drops f from the index
and leaves it on disk, and git then reports it untracked while HEAD
still carries it.
This is not “the new files in the working tree”. It is defined against
the index, so git add removes a file from it. A caller that unions
this with a HEAD-derived set has a hole exactly the size of “staged, not
yet committed” — which is issues #636, #649 and #657, three surfaces that
each made that union by hand and each gave a silently wrong answer. Use
Repo::added_since_head instead; it is that union, correct, in one place.
Respects .gitignore / .git/info/exclude / global excludes, skips nested
repositories and non-regular files (symlinks, dirs, submodules), and returns
repository-relative, unix-separated paths, sorted. Empty in a bare repo.
§Errors
Returns GitError on a git failure or a non-UTF-8 path.
Source§impl Repo
impl Repo
Sourcepub fn diff_trees(&self, old: &str, new: &str) -> Result<TreeDiff, GitError>
pub fn diff_trees(&self, old: &str, new: &str) -> Result<TreeDiff, GitError>
The blob-level diff between two tree object ids (old → new), pruning
unchanged subtrees: gix descends only into subtrees whose oid differs, so
the cost is proportional to the change, not the tree size. Renames are
reported as a delete plus an add (rewrite tracking is off), which is what
the path-scoped extractor wants. Results are sorted by path for determinism.
This is the incremental-sync counterpart to Repo::walk_blobs: given the
last-synced tree and HEAD, it yields exactly the paths that changed.
§Errors
Returns GitError if either id is not a tree, the diff fails, or a path
is not valid UTF-8.