Skip to main content

Repo

Struct Repo 

Source
pub struct Repo { /* private fields */ }
Expand description

A discovered git repository.

Implementations§

Source§

impl Repo

Source

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.

Source

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).

Source

pub fn git_dir(&self) -> &Path

This worktree’s git directory (per-worktree; the graph DB lives here).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn last_authors( &self, paths: &BTreeSet<String>, ) -> Result<BTreeMap<String, PathAuthor>, GitError>

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.

Source

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.

Source

pub fn walk_blobs(&self) -> Result<Vec<BlobRef>, GitError>

Every blob reachable from the HEAD tree, with full paths.

§Errors

Returns GitError if the tree cannot be traversed or a path is not valid UTF-8.

Source

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.

Source

pub fn tree_id_at(&self, rev: &str) -> Result<String, GitError>

The hex tree id an arbitrary revspec resolves to (a commit peels to its tree) — an O(1) resolution that does not walk the tree, so it doubles as a cheap “does this ref exist?” check (ADR-0009 step 8b/8c).

§Errors

Returns GitError if rev cannot be resolved to a tree.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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 — sync deleted a node from the graph on git add;
  • issue #649 — review said “no working-tree changes to review” on a tree with a staged addition in it;
  • issue #657 — check reported 0 violations on drift it had caught one git add earlier, 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.

Source

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

Source

pub fn diff_trees(&self, old: &str, new: &str) -> Result<TreeDiff, GitError>

The blob-level diff between two tree object ids (oldnew), 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.

Auto Trait Implementations§

§

impl !Freeze for Repo

§

impl !RefUnwindSafe for Repo

§

impl !Send for Repo

§

impl !Sync for Repo

§

impl !UnwindSafe for Repo

§

impl Unpin for Repo

§

impl UnsafeUnpin for Repo

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.