pub struct Repo<R: ProcessRunner = JobRunner> { /* private fields */ }Expand description
Implementations§
Source§impl Repo<JobRunner>
impl Repo<JobRunner>
Sourcepub fn discover(dir: impl AsRef<Path>) -> Result<Self>
pub fn discover(dir: impl AsRef<Path>) -> Result<Self>
Discover the repository at or above dir and open a handle bound to
dir, using the real job-backed runner. Walks up from dir toward the
filesystem root — see discover — so it finds a repository whose root
is dir itself or any ancestor. Errors with Error::NotARepository
when no .git/.jj is found, or with Error::BareRepository when the
walk instead reaches a bare git repository (git init --bare) before
any .jj/.git — a bare repo has no working tree for this facade to
drive (issue #6).
For a strict check of exactly dir — no walking up — see Repo::open.
Sourcepub fn open(dir: impl AsRef<Path>) -> Result<Self>
pub fn open(dir: impl AsRef<Path>) -> Result<Self>
Open the repository at exactly dir — unlike Repo::discover,
this does not walk up through parent directories: dir itself must
hold the .jj/.git marker (a .jj directory with a repo entry, or a
.git directory / gitlink file — the same validated markers discover
uses), or this errors with
Error::NotARepository(dir)
even if a repository exists somewhere above dir. Mirrors the
discover-vs-open split in gitoxide (gix::discover vs gix::open) and
libgit2 (git_repository_discover vs git_repository_open) — see
issue #8.
If dir itself is a bare git repository (git init --bare: no
.git subdirectory, just HEAD/config/objects/refs directly in
dir — see is_bare_git_repo_marker), this errors with
Error::BareRepository(dir) instead of the
generic NotARepository, matching what Repo::discover reports for
the same directory (issue #6) — open still never walks up, so this
only applies to dir itself, not an ancestor.
Source§impl<R: ProcessRunner> Repo<R>
impl<R: ProcessRunner> Repo<R>
Sourcepub fn from_git(
root: impl Into<PathBuf>,
cwd: impl Into<PathBuf>,
client: Git<R>,
) -> Self
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.
Sourcepub fn from_jj(
root: impl Into<PathBuf>,
cwd: impl Into<PathBuf>,
client: Jj<R>,
) -> Self
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.
Sourcepub fn discover_with<G, J>(dir: impl AsRef<Path>, git: G, jj: J) -> Result<Self>
pub fn discover_with<G, J>(dir: impl AsRef<Path>, git: G, jj: J) -> Result<Self>
Discover the repository at or above dir — exactly as Repo::discover —
but build the handle from a caller-injected client instead of the plain
default one. dir is absolutised and walked toward the filesystem root (see
discover), then the factory for the detected backend is invoked:
git for a .git repository, jj for a .jj one. Only the matching
closure runs — the other is never called, so neither client is built
speculatively.
This is the injected-client counterpart of Repo::discover: reach for it
when the handle needs a pre-configured client — a hardened Git, a
per-command timeout, a custom ProcessRunner test seam — rather than the
default Git::new / Jj::new that Repo::discover uses. It shares
Repo::discover’s exact detection and error classification, so a consumer
no longer has to re-implement the discover walk, match BackendKind,
and assemble from_git / from_jj by
hand just to inject clients.
Because the match on BackendKind lives here, inside the crate that
declares the enum #[non_exhaustive], adding a future backend variant is a
change to this one method — callers need no wildcard/catch-all arm of their
own to keep in sync.
§Errors
The same as Repo::discover: Error::NotARepository when no .git/.jj
marker is found from dir up to the filesystem root, or
Error::BareRepository when the walk instead reaches a bare git
repository (git init --bare) first. It reuses the very same private
find_bare_git_repo diagnostic path as Repo::discover, so the bare-repo
distinction is reported identically no matter which entry point opened the
repository.
// A hardened git client / timeout-bound jj client, injected lazily — only
// the one matching the detected backend is ever built.
let repo = Repo::discover_with(
".",
|| Git::hardened().default_timeout(Duration::from_secs(120)),
|| Jj::new().default_timeout(Duration::from_secs(120)),
)?;Sourcepub fn kind(&self) -> BackendKind
pub fn kind(&self) -> BackendKind
Which backend drives this handle.
Sourcepub fn at(&self, dir: impl Into<PathBuf>) -> Self
pub fn at(&self, dir: impl Into<PathBuf>) -> Self
A sibling handle bound to dir, sharing this handle’s client and root.
Sourcepub fn git(&self) -> Option<&Git<R>>
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.
Sourcepub fn git_at(&self) -> Option<GitAt<'_, R>>
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?;Sourcepub async fn current_branch(&self) -> Result<Option<String>>
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 @.
Sourcepub async fn trunk(&self) -> Result<Option<String>>
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.
Sourcepub async fn local_branches(&self) -> Result<Vec<String>>
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 renders as a tombstone row (jj keeps it internally so the
deletion can be propagated) until the deletion is pushed. This tombstone is
filtered out here — a name a delete_branch just removed does not
reappear in the result — while a conflicted bookmark (present, but with no
single normal target) is still reported as existing, since dropping it too
would hide a real, live bookmark that merely has a conflict (T-041; was M21).
Sourcepub async fn local_branches_readonly(&self) -> Result<Vec<String>>
pub async fn local_branches_readonly(&self) -> Result<Vec<String>>
A read-only local_branches: the same result,
but on jj it passes --ignore-working-copy, so listing the bookmarks
records no jj operation and never moves @. On git it is exactly
local_branches — git’s branch listing records no
operation and moves no ref, so there is nothing to make read-only.
Use it (with snapshot_readonly) from an
observer — a watcher or a prompt refresh — that must not perturb the
state it reads. See snapshot_readonly for the
jj working-copy trade-off this shares.
Sourcepub async fn branch_exists(&self, name: &str) -> Result<bool>
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: on jj, a bookmark just removed by delete_branch but
still tracked on a remote does not read as existing here (the tombstone is
filtered), while a conflicted bookmark still does.
Sourcepub async fn has_uncommitted_changes(&self) -> Result<bool>
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 @).
Sourcepub async fn has_tracked_changes(&self) -> Result<bool>
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.
Sourcepub async fn conflicted_files(&self) -> Result<Vec<PathBuf>>
pub async fn conflicted_files(&self) -> Result<Vec<PathBuf>>
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. Each path is a PathBuf carried losslessly from
the backend, so a non-UTF-8 conflicted filename (legal on Unix) is not
corrupted to U+FFFD.
Sourcepub async fn create_branch(&self, name: &str) -> Result<()>
pub async fn create_branch(&self, name: &str) -> Result<()>
Create a local branch (git) / bookmark (jj) at the current head, without
switching the working copy (git branch <name>; jj bookmark create <name> -r @).
Sourcepub async fn delete_branch(&self, spec: BranchDelete) -> Result<()>
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.
Sourcepub async fn rename_branch(&self, old: &str, new: &str) -> Result<()>
pub async fn rename_branch(&self, old: &str, new: &str) -> Result<()>
Rename a local branch (git) / bookmark (jj).
Sourcepub async fn changed_files(&self) -> Result<Vec<FileChange>>
pub async fn changed_files(&self) -> Result<Vec<FileChange>>
The working-copy changes (git status / jj diff -r @ --summary).
Sourcepub async fn diff_stat(&self) -> Result<DiffStat>
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.
jj snapshot caveat: like every other jj-backed read here (status,
changed_files, snapshot, log, …), this runs a plain jj diff — jj’s
default mode, which first snapshots the working copy (imports any bare
filesystem edit into a fresh @) and records a new operation in the op
log. That is a bookkeeping side effect, not a content mutation (no tracked
file/ref changes, and it is transparently undoable via jj op undo), but it
is not a no-op read either. A genuinely non-recording read exists
(--ignore-working-copy, wired up as vcs_jj’s _ignoring_working_copy
client methods and this crate’s snapshot_readonly
/ local_branches_readonly, built for vcs-watch’s
polling loop) but is deliberately not used here: it reports the state of
the last recorded operation rather than the live working tree, so a bare
edit no jj command has yet snapshotted would be silently invisible — wrong
for a method whose whole purpose is reporting the current working-copy
state.
Sourcepub async fn diff(&self) -> Result<Vec<FileDiff>>
pub async fn diff(&self) -> Result<Vec<FileDiff>>
The full parsed diff for the working copy — the same scope as
diff_stat (git: working tree vs HEAD, using the
empty-tree oid on an unborn repo; jj: @ vs its parent), but returning the
per-file hunks/lines (FileDiff) rather than just the aggregate counts.
Dispatches to the already-existing GitApi::diff/JjApi::diff with
vcs_git::DiffSpec::WorkingTree, so it inherits the backend client’s
OutputBudget — an over-budget diff errors with
OutputTooLarge rather than
buffering (or silently truncating) an unbounded diff.
Backend nuance (same as diff_stat): git diffs the working tree against
HEAD, which excludes untracked files; jj diffs @ against its
parent, which includes newly-added files. So a brand-new file shows in
changed_files but not here on git, whereas on jj
it shows in both — don’t assume the two backends return the same file set.
Cross-backend revision-range diffs are deliberately not exposed here
(see the crate docs’ “what’s deliberately not unified” — range diffs stay
on the raw git/jj client, via GitApi::diff/
JjApi::diff with DiffSpec::Rev).
Same jj snapshot caveat as diff_stat: on jj this is a
plain jj diff -r @ --git, jj’s default working-copy-snapshotting mode
(records an operation in the op log — a reversible bookkeeping side effect,
not a tracked-content mutation), deliberately not the non-recording
--ignore-working-copy mode, which would make this method blind to any not-
yet-snapshotted edit.
Sourcepub async fn log(
&self,
revspec_or_revset: &str,
max: usize,
) -> Result<Vec<Commit>>
pub async fn log( &self, revspec_or_revset: &str, max: usize, ) -> Result<Vec<Commit>>
Recent history: up to max commits reachable from revspec_or_revset
(git revspec / jj revset), most-recent-first (git log’s default order /
jj log’s topological order).
Backend nuance: Commit::author/Commit::date are Some only on
git — jj’s typed log doesn’t currently surface authorship or a
timestamp, so they’re None there rather than guessed (see the
Commit type docs).
Sourcepub async fn annotate(
&self,
path: &str,
rev: Option<&str>,
) -> Result<Vec<AnnotationLine>>
pub async fn annotate( &self, path: &str, rev: Option<&str>, ) -> Result<Vec<AnnotationLine>>
Per-line attribution for path, optionally at rev (a git revspec / jj
revset). None reads the current git HEAD / jj @; a supplied revision is
passed to the selected backend without facade-level interpretation.
Backend nuance: AnnotationLine::author/AnnotationLine::date are
Some only on git — jj’s typed annotation exposes only the introducing
change and line content, so the facade leaves them None rather than guessing
(see AnnotationLine).
Sourcepub async fn show_file(&self, rev: &str, path: &str) -> Result<String>
pub async fn show_file(&self, rev: &str, path: &str) -> Result<String>
The content of path as it exists at rev (git revspec / jj revset), e.g.
HEAD:src/lib.rs on git or @- + a fileset on jj — both normalise
backslash path separators and return the file’s bytes verbatim (including
any trailing newline).
Sourcepub async fn show_file_within(
&self,
rev: &str,
path: &str,
budget: OutputBudget,
) -> Result<String>
pub async fn show_file_within( &self, rev: &str, path: &str, budget: OutputBudget, ) -> Result<String>
show_file with an explicit per-call OutputBudget,
instead of the budget the backend client was built with
(default_output_budget, inherited
through from_git/from_jj). Reads the
blob under budget: past the ceiling it errors with an
OutputTooLarge-carrying
Error::Vcs (actual and allowed sizes) rather than buffering an unbounded
file — use it to read a legitimately large file
(OutputBudget::unlimited, or a higher cap) or to tighten the cap for one
call. A truncated blob is never returned as if complete.
Sourcepub async fn snapshot(&self) -> Result<RepoSnapshot>
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.
Sourcepub async fn snapshot_readonly(&self) -> Result<RepoSnapshot>
pub async fn snapshot_readonly(&self) -> Result<RepoSnapshot>
A read-only snapshot: the same RepoSnapshot,
but on jj it never snapshots the working copy — every underlying query
passes --ignore-working-copy, so the batched read records no jj
operation and never moves @. On git it is exactly
snapshot (git’s status query records no operation and
moves no ref).
Use it for an observer — a repository watcher, a prompt/status-bar
refresh — that must not perturb the state it reports: an ordinary jj query
snapshots the working copy as a side effect (taking the lock, recording an
operation, possibly moving @), so the observer would otherwise mutate
the repo it merely means to read.
jj trade-off: because the working copy isn’t snapshotted, a bare
working-tree edit that no jj command has recorded yet is not reflected
— dirty/head are as of the
last recorded operation. To observe such unsnapshotted edits, accept the
mutation and call snapshot.
Sourcepub async fn commit_paths(&self, paths: &[PathBuf], message: &str) -> Result<()>
pub async fn commit_paths(&self, paths: &[PathBuf], 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.
Takes PathBufs so a path obtained from changed_files
/ conflicted_files round-trips losslessly — on
git a non-UTF-8 path (legal on Unix) reaches the commit unchanged via the
NUL-safe pathspec transport; on jj the fileset language is text, so jj’s own
(non-UTF-8-incapable) fileset handling applies.
Sourcepub async fn fetch(&self) -> Result<()>
pub async fn fetch(&self) -> Result<()>
Fetch from the default remote (git fetch / jj git fetch).
Sourcepub async fn fetch_from(&self, remote: &str) -> Result<()>
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.
Sourcepub async fn fetch_branch(&self, branch: &str) -> Result<()>
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.
Sourcepub async fn push(&self, branch: &str) -> Result<()>
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.
Sourcepub async fn checkout(&self, reference: &str) -> Result<()>
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, use new_child, which maps to jj new <reference> on jj and to checkout <reference> on git.
Sourcepub async fn new_child(&self, reference: &str) -> Result<()>
pub async fn new_child(&self, reference: &str) -> Result<()>
Start new work on top of reference without modifying it.
On git this checks out reference; the next commit naturally appends on top.
On jj this runs jj new <reference>, creating an undescribed child change.
Sourcepub async fn rebase(&self, onto: &str) -> Result<()>
pub async fn rebase(&self, onto: &str) -> Result<()>
Rebase the current line onto onto. The two backends diverge on
non-linear layouts, so this is a documented least-common-denominator:
- git (
rebase <onto>=merge-base(HEAD,onto)..HEAD) moves onlyHEAD’s own ancestor line; commits stacked onHEADstay put. - jj (
rebase -d <onto>= the default-b @=(onto..@)::) moves that line and its whole descendant closure — anything stacked on@, and any sibling off an intermediate commit of the line, move too.
They agree on a linear HEAD/@; on a stacked or intermediate-fork
layout jj moves strictly more. A sibling that shares only the fork point is
moved by neither. onto is a branch/bookmark name or revision the backend
understands.
Sourcepub async fn try_merge(&self, source: &str) -> Result<MergeProbe>
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-safe rollback: on both backends the whole rollback
path — the decision of whether to roll back and the command that
performs it — runs on a fresh cancellation context with its own bounded
deadline (git:
Git::is_merge_in_progress_detached+merge --abortviaGit::merge_abort_detached; jj: the op-log probe +op restoreviaJj::rollback_to), so adefault_cancel_ontoken (thecancellationfeature) that fires during the probe no longer cancels the rollback too — not even by cancelling the “is a trial merge still staged?” check before the abort is reached. The trial merge is still undone rather than left staged, closing the gap where a cancelled probe abandoned it on git. (A rollback that fails for another reason still propagates per the bullet above.)
Sourcepub async fn abort_in_progress(&self) -> Result<OperationState>
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.
Sourcepub async fn continue_in_progress(&self) -> Result<OperationState>
pub async fn continue_in_progress(&self) -> Result<OperationState>
Continue the in-progress operation after conflict resolution (git:
commit --no-edit for a merge, or the matching --continue for a rebase /
am / cherry-pick / revert; jj: a no-op — resolving the files is the
continuation). A git bisect has no such step and is refused with
Error::Unsupported rather than silently reported still in progress.
Returns the fresh post-call OperationState:
Conflictwhen unresolved paths still block continuing (also on git — unlikein_progress_state, this method does reportConflictfor git), or when a continued rebase stops on the next patch’s conflict.Clearwhen the operation finished.
Sourcepub async fn in_progress_state(&self) -> Result<OperationState>
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.
Sourcepub async fn list_worktrees(&self) -> Result<Vec<WorktreeInfo>>
pub async fn list_worktrees(&self) -> Result<Vec<WorktreeInfo>>
List attached worktrees (git) / workspaces (jj).
Sourcepub async fn create_worktree(
&self,
spec: WorktreeCreate,
) -> Result<CreateOutcome>
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),
then the workspace is forgotten. Residue is no longer swallowed: if the
rollback can’t remove that directory or can’t forget the workspace, the call
fails with a composite Error::Io naming what still needs cleaning up (and
is safe to re-run); a clean rollback instead surfaces the original
bookmark-step error unchanged (its Error::Vcs classification) — so a failed
call never silently leaks a half-made worktree.
Sourcepub async fn remove_worktree(&self, spec: WorktreeRemove) -> Result<()>
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 none of the resolvable jj workspaces returns
Error::WorktreeNotFound, but when some registered workspace can’t be
resolved via jj workspace root --name the path’s absence is unprovable, so a
distinct diagnosable Error::Io (naming the unresolved workspaces;
is_resource_not_found stays false) is
returned instead. A directory that can’t be deleted is likewise surfaced (an
Error::Io naming the still-registered workspace, with the forget left for
the retry). (For the short-lived, blocking Drop-path 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.
Sourcepub fn cleanup_worktree_blocking(&self, path: &Path) -> Result<()>
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). Short-lived and shells out directly
(no job-containment), but not error-swallowing: a jj path that genuinely
matches no workspace is an Ok no-op, yet a probe failure (the workspace list, or a registered workspace that won’t resolve) and a remove_dir_all
failure are surfaced as Err (the forget is skipped on a failed removal, so
a surviving directory isn’t orphaned). 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> Debug for Repo<R>
impl<R: ProcessRunner> Debug for Repo<R>
Source§impl<R: ProcessRunner> VcsRepo for Repo<R>
impl<R: ProcessRunner> VcsRepo for Repo<R>
Source§fn kind(&self) -> BackendKind
fn kind(&self) -> BackendKind
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,
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,
Repo::current_branch.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,
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,
Repo::trunk.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,
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,
Repo::local_branches.Source§fn local_branches_readonly<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<String>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn local_branches_readonly<'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,
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,
Repo::branch_exists.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,
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,
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<PathBuf>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn conflicted_files<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<PathBuf>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Source§fn create_branch<'life0, 'life1, 'async_trait>(
&'life0 self,
name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn create_branch<'life0, 'life1, 'async_trait>(
&'life0 self,
name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Repo::create_branch.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,
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,
Repo::delete_branch.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,
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,
Repo::rename_branch.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,
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,
Repo::changed_files.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,
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,
Repo::diff_stat.Source§fn diff<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<FileDiff>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn diff<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Vec<FileDiff>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Repo::diff.Source§fn log<'life0, 'life1, 'async_trait>(
&'life0 self,
revspec_or_revset: &'life1 str,
max: usize,
) -> Pin<Box<dyn Future<Output = Result<Vec<Commit>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn log<'life0, 'life1, 'async_trait>(
&'life0 self,
revspec_or_revset: &'life1 str,
max: usize,
) -> Pin<Box<dyn Future<Output = Result<Vec<Commit>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Repo::log.Source§fn show_file<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
rev: &'life1 str,
path: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn show_file<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
rev: &'life1 str,
path: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Repo::show_file.Source§fn annotate<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
path: &'life1 str,
rev: Option<&'life2 str>,
) -> Pin<Box<dyn Future<Output = Result<Vec<AnnotationLine>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn annotate<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
path: &'life1 str,
rev: Option<&'life2 str>,
) -> Pin<Box<dyn Future<Output = Result<Vec<AnnotationLine>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Repo::annotate.Source§fn show_file_within<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
rev: &'life1 str,
path: &'life2 str,
budget: OutputBudget,
) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn show_file_within<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
rev: &'life1 str,
path: &'life2 str,
budget: OutputBudget,
) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: '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,
fn snapshot<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<RepoSnapshot>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Repo::snapshot.Source§fn snapshot_readonly<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<RepoSnapshot>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn snapshot_readonly<'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 [PathBuf],
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,
fn commit_paths<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
paths: &'life1 [PathBuf],
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,
Repo::commit_paths.Source§fn fetch<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn fetch<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Repo::fetch.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,
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,
Repo::fetch_from.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,
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,
Repo::fetch_branch.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,
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,
Repo::push.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,
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,
Repo::checkout.Source§fn new_child<'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,
fn new_child<'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,
Repo::new_child.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,
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,
Repo::rebase.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,
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,
Repo::try_merge.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,
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,
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,
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,
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,
Repo::list_worktrees.