Skip to main content

Repo

Struct Repo 

Source
pub struct Repo<R: ProcessRunner = JobRunner> { /* private fields */ }
Expand description

A cwd-bound, backend-agnostic VCS handle. Operations run against the bound directory (cwd); use at to get a sibling handle bound elsewhere.

Implementations§

Source§

impl Repo<JobRunner>

Source

pub fn open(dir: impl AsRef<Path>) -> Result<Self>

Detect the repository at or above dir and open a handle bound to dir, using the real job-backed runner. Errors with Error::NotARepository when no .git/.jj is found.

Source§

impl<R: ProcessRunner> Repo<R>

Source

pub fn from_git( root: impl Into<PathBuf>, cwd: impl Into<PathBuf>, client: Git<R>, ) -> Self

Build a git-backed handle from an explicit client — for a custom runner (e.g. a test seam) or a pre-configured Git.

Source

pub fn from_jj( root: impl Into<PathBuf>, cwd: impl Into<PathBuf>, client: Jj<R>, ) -> Self

Build a jj-backed handle from an explicit client.

Source

pub fn kind(&self) -> BackendKind

Which backend drives this handle.

Source

pub fn root(&self) -> &Path

The repository root detected at open time.

Source

pub fn cwd(&self) -> &Path

The directory operations run against.

Source

pub fn at(&self, dir: impl Into<PathBuf>) -> Self

A sibling handle bound to dir, sharing this handle’s client and root.

Source

pub fn git(&self) -> Option<&Git<R>>

The underlying Git client, or None when jj-backed — an escape hatch to git-only operations not on the common surface.

Source

pub fn jj(&self) -> Option<&Jj<R>>

The underlying Jj client, or None when git-backed.

Source

pub fn git_at(&self) -> Option<GitAt<'_, R>>

The git client bound to this handle’s cwd — a GitAt whose methods omit the dir argument — or None when jj-backed. The dir-free counterpart of git: repo.git_at()?.merge_continue().await?.

The returned view borrows self. To work in another worktree, bind the re-anchored handle first (the view can’t outlive a temporary at):

let wt = repo.at(wt);          // owns the re-anchored handle
let git = wt.git_at().unwrap();
git.fetch().await?;
Source

pub fn jj_at(&self) -> Option<JjAt<'_, R>>

The jj client bound to this handle’s cwd — a JjAt whose methods omit the dir argument — or None when git-backed. The dir-free counterpart of jj. For another workspace, bind the re-anchored handle first (let ws = repo.at(path); ws.jj_at()…) — see git_at.

Source

pub async fn current_branch(&self) -> Result<Option<String>>

The current branch (git) or bookmark (jj). On jj this is the nearest bookmark reachable from the working copy (heads(::@ & bookmarks())), so it stays set across a jj describe/jj new/jj commit — which leave the bookmark on the described parent while the new change carries none — matching git’s “still on my branch” reporting. When several bookmarks are equally near @, the lexicographically-smallest name is returned (deterministic). None only when detached / no bookmark on or above @.

Source

pub async fn trunk(&self) -> Result<Option<String>>

The trunk branch/bookmark. Resolution order: the backend’s own notion (git’s origin/HEAD, jj’s trunk() revset), then a fallback to a local main, then master; None when none of those resolve.

Source

pub async fn local_branches(&self) -> Result<Vec<String>>

Local branch (git) / bookmark (jj) names.

Backend divergence: on jj, a bookmark deleted locally but still tracked on a remote lingers as a tombstone row (jj keeps it so the deletion can be propagated) until the deletion is pushed — so this can list a name a delete_branch just removed, unlike git. (The tombstone is not filtered here because jj renders it and a conflicted bookmark identically — filtering would also hide a real, conflicted bookmark; M21.)

Source

pub async fn branch_exists(&self, name: &str) -> Result<bool>

Whether a local branch/bookmark named name exists. See local_branches for the jj deleted-but-tracked tombstone divergence (a just-deleted tracked bookmark can still read as existing until the deletion is pushed).

Source

pub async fn has_uncommitted_changes(&self) -> Result<bool>

Whether the working copy has uncommitted changes (git: a non-empty status; jj: a non-empty working-copy change @).

Source

pub async fn has_tracked_changes(&self) -> Result<bool>

Whether the working copy has uncommitted changes to tracked files.

Backend nuance: git ignores untracked files here (status --untracked-files=no); jj auto-tracks new files, so there is no untracked concept and this equals has_uncommitted_changes.

Source

pub async fn conflicted_files(&self) -> Result<Vec<String>>

Paths with unresolved merge conflicts in the working copy, repo-relative with / separators (git diff --diff-filter=U / jj resolve --list -r @). Empty when there are none.

Source

pub async fn delete_branch(&self, spec: BranchDelete) -> Result<()>

Delete a local branch (git) / bookmark (jj). The BranchDelete spec’s force applies to git only (branch -D vs -d); jj has no force and ignores it.

Source

pub async fn rename_branch(&self, old: &str, new: &str) -> Result<()>

Rename a local branch (git) / bookmark (jj).

Source

pub async fn changed_files(&self) -> Result<Vec<FileChange>>

The working-copy changes (git status / jj diff -r @ --summary).

Source

pub async fn diff_stat(&self) -> Result<DiffStat>

Aggregate insertion/deletion counts for the working copy.

Backend nuance: git counts the working tree against HEAD (git diff, which excludes untracked files), while jj counts the @ change against its parent (which includes newly-added files). So on git a brand-new file shows in changed_files but not here, whereas on jj it shows in both. On an unborn git repo (no commits yet) the count is taken against the empty tree, so a pre-first-commit working tree stats instead of erroring.

Source

pub async fn snapshot(&self) -> Result<RepoSnapshot>

A batched RepoSnapshot of the common repo state — branch, upstream, ahead/behind, dirtiness, change count, and operation state — in a small fixed number of spawns instead of a call per field (git: status --porcelain=v2 --branch + the in-progress probe; jj: a log -r @ template for head/empty/conflict, a reachable_bookmarks query for branch, and a change count only when dirty). Built for prompt/status-bar/ TUI refreshes. Note the asymmetry: tracking (the upstream ref + ahead/behind) is always None on jj, which has no git-style upstream tracking.

Source

pub async fn commit_paths(&self, paths: &[String], message: &str) -> Result<()>

Commit exactly paths with message (git commit --only, jj commit <filesets>). Paths are repo-relative. paths must be non-empty: an empty set is refused up front, because the backends would diverge dangerously — git errors out, while jj’s commit with no filesets would silently commit the entire working copy.

Source

pub async fn fetch(&self) -> Result<()>

Fetch from the default remote (git fetch / jj git fetch).

Source

pub async fn fetch_from(&self, remote: &str) -> Result<()>

Fetch from a named remote (git fetch <remote> / jj git fetch --remote <remote>). Transient network failures are retried by the underlying client.

Source

pub async fn fetch_branch(&self, branch: &str) -> Result<()>

Fetch a single branch/bookmark from origin into its remote-tracking ref (git fetch_branch / jj git fetch -b). Transient network failures are retried by the underlying client.

Source

pub async fn push(&self, branch: &str) -> Result<()>

Push branch to origin (git push -u origin <branch> / jj git push -b <branch>).

The branch (jj: bookmark) must already exist locally. The two backends honestly differ in what “push” means: git pushes the ref and records the upstream (-u; idempotent on repeat pushes), while jj pushes the bookmark’s state — including deleting the remote branch if the bookmark was deleted locally. Renamed refspecs (local:remote) and non-origin remotes are git-only concepts; use the git() escape hatch (vcs_git::GitPush) for those.

Source

pub async fn checkout(&self, reference: &str) -> Result<()>

Switch the working copy to reference (git checkout / jj edit).

Backend divergence — this is not “detach and build on top” on jj. On git, a subsequent commit appends on top of reference (its tip is untouched). On jj, checkout maps to jj edit, which makes reference’s commit itself the working-copy change — so a following commit_paths (or any edit) rewrites that commit in place (a new change-id, a replaced description), silently amending a possibly-already-pushed commit rather than adding a new one.

So backend-agnostic “start fresh work on top of main” code must not rely on checkout alone. If you want git-like append-on-top semantics on both backends, start a new child change explicitly — on jj that is jj new <reference> via the raw jj client (a first-class new_child facade primitive is planned); on git, checkout already appends.

Source

pub async fn rebase(&self, onto: &str) -> Result<()>

Rebase the current work onto onto (git rebase / jj rebase -d). The onto is a branch/bookmark name or revision the backend understands.

Source

pub async fn try_merge(&self, source: &str) -> Result<MergeProbe>

Probe whether merging source into the current work would conflict, without leaving any trace: the probe is rolled back before returning (git: merge --no-commit --no-ff then merge --abort; jj: a merge change probed and undone via op restore).

Preconditions/behaviour:

  • git: requires a clean-enough working tree — a dirty-tree refusal propagates as a plain error, not as MergeProbe::Conflicts.
  • A failing rollback propagates as an error rather than returning a result that misdescribes the on-disk state.
  • Cancellation caveat: the rollback runs on the same client, so if the client carries a default_cancel_on token (the cancellation feature) that fires during the probe, the rollback command is cancelled too and the probe change may be left behind (Error::Cancelled surfaces). Re-probe and reset with an un-cancelled client if you need a clean tree.
Source

pub async fn abort_in_progress(&self) -> Result<OperationState>

Abort the in-progress operation, if any (git: merge --abort / rebase --abort; jj: a no-op — there are no paused operations, roll back explicitly via Jj::transaction / op_restore). Returns the fresh post-call OperationState; Clear when nothing was (or remains) in progress.

Source

pub async fn continue_in_progress(&self) -> Result<OperationState>

Continue the in-progress operation after conflict resolution (git: commit --no-edit for a merge / rebase --continue; jj: a no-op — resolving the files is the continuation). Returns the fresh post-call OperationState:

  • Conflict when unresolved paths still block continuing (also on git — unlike in_progress_state, this method does report Conflict for git), or when a continued rebase stops on the next patch’s conflict.
  • Clear when the operation finished.
Source

pub async fn in_progress_state(&self) -> Result<OperationState>

Whether the working copy is mid-operation or conflicted — see OperationState. Lets a caller decide between abort/continue without knowing the backend’s model. Note the asymmetry: this method reports Merge/Rebase (never Conflict) on git — a git conflict is that paused state, and the conflict itself surfaces on the failed op via Error::is_merge_conflict (or as Conflict from continue_in_progress) — while jj has no paused op and reports Conflict directly.

Source

pub async fn list_worktrees(&self) -> Result<Vec<WorktreeInfo>>

List attached worktrees (git) / workspaces (jj).

Source

pub async fn create_worktree( &self, spec: WorktreeCreate, ) -> Result<CreateOutcome>

Create a worktree/workspace at path on a new branch based on base. Always CreateOutcome::Plain; a copy-on-write strategy stays in the consumer.

branch must not already exist. The jj path is two steps (workspace add then bookmark create) and is not atomic, but a failed bookmark step rolls back: the workspace directory is removed only when workspace add created it (a pre-existing directory the caller already had is left intact), the workspace is forgotten best-effort, and the original error is surfaced — so a failed call doesn’t leak a half-made worktree.

Source

pub async fn remove_worktree(&self, spec: WorktreeRemove) -> Result<()>

Remove the worktree/workspace at path. For jj this resolves the workspace name by matching path, deletes the directory, then forgets it; a path that matches no attached jj workspace returns Error::WorktreeNotFound. (For the best-effort, never-erroring variant, see cleanup_worktree_blocking.)

The WorktreeRemove spec’s force mirrors git’s worktree remove: without it a worktree that still has uncommitted changes is refused (Err) rather than deleted, so a stray edit isn’t silently lost — build WorktreeRemove::new(path).force() to remove it anyway. On jj the changes are snapshotted into the op log before the check, so a refusal keeps them recoverable; note that checking spawns a jj command in the target workspace, so a genuinely stale working copy can surface an error without force (use .force() there). The repository’s main workspace is always refused (it can’t be removed without destroying the repo), regardless of force.

Source

pub fn cleanup_worktree_blocking(&self, path: &Path) -> Result<()>

Synchronous worktree cleanup for a context that cannot .await — chiefly a Drop guard. Force-removes the worktree at path (git: worktree remove --force; jj: resolve the workspace name by path, delete the directory, then workspace forget). Best-effort and short-lived: it shells out directly (no job-containment); a jj path that matches no workspace is a no-op (Ok). Like the async remove_worktree, it refuses the repository’s main workspace (whose directory is the main working copy) — deleting it would wipe the repo — even on this force-by-contract path.

Trait Implementations§

Source§

impl<R: ProcessRunner> VcsRepo for Repo<R>

Source§

fn kind(&self) -> BackendKind

Which backend drives this handle.
Source§

fn root(&self) -> &Path

The repository root detected at open time.
Source§

fn cwd(&self) -> &Path

The directory operations run against.
Source§

fn cleanup_worktree_blocking(&self, path: &Path) -> Result<()>

Source§

fn current_branch<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Option<String>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn trunk<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Option<String>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn local_branches<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Vec<String>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn branch_exists<'life0, 'life1, 'async_trait>( &'life0 self, name: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<bool>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn has_uncommitted_changes<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<bool>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn has_tracked_changes<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<bool>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn conflicted_files<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Vec<String>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn delete_branch<'life0, 'async_trait>( &'life0 self, spec: BranchDelete, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn rename_branch<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, old: &'life1 str, new: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Source§

fn changed_files<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Vec<FileChange>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn diff_stat<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<DiffStat>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn snapshot<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<RepoSnapshot>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn commit_paths<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, paths: &'life1 [String], message: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Source§

fn fetch<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn fetch_from<'life0, 'life1, 'async_trait>( &'life0 self, remote: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn fetch_branch<'life0, 'life1, 'async_trait>( &'life0 self, branch: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn push<'life0, 'life1, 'async_trait>( &'life0 self, branch: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn checkout<'life0, 'life1, 'async_trait>( &'life0 self, reference: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn rebase<'life0, 'life1, 'async_trait>( &'life0 self, onto: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn try_merge<'life0, 'life1, 'async_trait>( &'life0 self, source: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<MergeProbe>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn abort_in_progress<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<OperationState>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn continue_in_progress<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<OperationState>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn in_progress_state<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<OperationState>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn list_worktrees<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Vec<WorktreeInfo>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn create_worktree<'life0, 'async_trait>( &'life0 self, spec: WorktreeCreate, ) -> Pin<Box<dyn Future<Output = Result<CreateOutcome>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Source§

fn remove_worktree<'life0, 'async_trait>( &'life0 self, spec: WorktreeRemove, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Auto Trait Implementations§

§

impl<R = JobRunner> !RefUnwindSafe for Repo<R>

§

impl<R = JobRunner> !UnwindSafe for Repo<R>

§

impl<R> Freeze for Repo<R>

§

impl<R> Send for Repo<R>

§

impl<R> Sync for Repo<R>

§

impl<R> Unpin for Repo<R>

§

impl<R> UnsafeUnpin for Repo<R>

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> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

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.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more