Skip to main content

GitModule

Struct GitModule 

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

Git module instance, tied to a Session.

Tracks which worktrees and branches were created by this session. Write operations check ownership before proceeding; read operations bypass the check entirely.

Implementations§

Source§

impl GitModule

Source

pub async fn status(&self) -> Result<StatusOutput>

Inspect the working tree and produce a StatusOutput split into staged / unstaged / untracked buckets.

Runs the libgit2 work on the tokio::task::spawn_blocking pool.

Source

pub async fn log(&self, filters: LogFilters) -> Result<LogOutput>

Walk HEAD backwards, applying LogFilters and returning at most filters.max_count matching commits.

Runs the libgit2 revwalk on the tokio::task::spawn_blocking pool.

Source

pub async fn diff(&self, staged: bool) -> Result<DiffOutput>

Produce the patch for either the staged (HEAD vs index) or the unstaged (index vs worktree) view, controlled by staged.

Runs the libgit2 diff on the tokio::task::spawn_blocking pool.

Source

pub async fn worktree_list(&self) -> Result<WorktreeListOutput>

List every linked worktree, annotated with session-ownership.

Source§

impl GitModule

Source

pub async fn fetch( &self, remote: Option<&str>, refspec: Option<&str>, prune: bool, ) -> Result<FetchOutput>

Fetch from a remote.

remote defaults to "origin". refspec is forwarded verbatim when present. prune adds --prune so deleted upstream refs are removed locally. The combined stdout + stderr is returned as raw for transport diagnostics — successful fetches usually print nothing on stdout but emit From <url> / <ref> -> <ref> lines on stderr.

Source

pub async fn remote_list(&self) -> Result<RemoteListOutput>

Enumerate remotes with their fetch / push URLs.

We use git remote -v rather than git2’s Repository::remotes so the output matches what git itself reports (and naturally handles the case where fetch and push URLs diverge via pushurl).

Source

pub async fn branch_status( &self, branch: &str, base: &str, ) -> Result<BranchStatusOutput>

Compare branch against base and report ahead / behind counts plus the merge-base, using git2’s graph_ahead_behind for the count and a git merge-base shell-out for the common-ancestor sha (git2’s merge_base returns an OID but we want the textual form).

Source

pub async fn unpushed_commits( &self, branch: &str, remote: &str, ) -> Result<UnpushedCommitsOutput>

List commits that exist on branch but not on <remote>/<branch>.

Source

pub async fn is_pushed( &self, commit: &str, remote: &str, ) -> Result<IsPushedOutput>

Check whether commit is reachable from any remote-tracking ref under refs/remotes/<remote>/.

Source

pub async fn tag_pushed( &self, tag: &str, remote: &str, ) -> Result<TagPushedOutput>

Check whether tag exists on remote (via git ls-remote --tags).

Source

pub async fn worktree_state( &self, branch: Option<&str>, ) -> Result<WorktreeStateOutput>

Snapshot a worktree’s state (branch, tracking, ahead/behind, uncommitted).

Source§

impl GitModule

Source

pub async fn reset( &self, working_dir: &Path, mode: ResetMode, target: &str, force: bool, ) -> Result<ResetOutput>

Move HEAD to target, with mode controlling the working tree behaviour. The working directory MUST be owned by the current session — see GitModule::ensure_session_scope.

force only affects Hard: without it, a hard reset is refused when the repository has stash entries and the working tree is dirty — the signature of “a stash was applied and is being cleaned up with the biggest hammer available”, which is precisely how stashed work gets destroyed. GitModule::stash_abort is the non-destructive undo; force = true is for callers who mean the reset regardless.

Source§

impl GitModule

Source

pub async fn session_release(&mut self) -> Result<SessionReleaseOutput>

Adopt orphan worktrees under the session-scoped worktrees dir (those known to git worktree list but not yet owned by this session). Any branches currently checked out inside them are adopted at the same time so the normal branch_delete ownership check will pass. The worktrees dir is resolved by [Session::worktrees_dir].

Returns the set of worktrees / branches that were newly adopted. Already-owned entries are skipped silently — calling this repeatedly is idempotent.

Source§

impl GitModule

Source

pub async fn stash_list(&self) -> Result<StashListOutput>

List every stash entry, newest first.

Read-only, so no ownership check — the entries belong to the repository, not to a session. Runs the libgit2 walk on the tokio::task::spawn_blocking pool.

Source

pub async fn stash_show(&self, index: usize) -> Result<StashShowOutput>

Show what stash@{index} would restore: the patch against the commit the stash was taken on, plus the untracked paths it carries.

Read-only; nothing is applied. Runs the libgit2 diff on the tokio::task::spawn_blocking pool.

Source

pub async fn stash_apply( &self, working_dir: &Path, index: usize, expected_sha: Option<String>, ) -> Result<StashApplyOutput>

Restore stash@{index} into working_dir without dropping it.

Preconditions, all of them refusals rather than best-effort merges:

  1. expected_sha (when given) must match the entry at index. The index shifts on every drop, the sha does not — pass it whenever the caller resolved the entry in an earlier turn.
  2. working_dir must have no staged and no unstaged changes. Untracked files are fine. This is what makes the rollback below total: everything the working tree gains came from the stash, so undoing the apply can never eat a hand edit.
  3. None of the entry’s untracked paths may already exist on disk — git would refuse halfway through and leave a partial apply.

On failure (conflict, or anything else git stash apply reports) the working tree is rolled back automatically and the error says so. The entry is never touched, so a failed apply costs nothing.

Source

pub async fn stash_abort( &self, working_dir: &Path, index: usize, expected_sha: Option<String>, ) -> Result<StashAbortOutput>

Undo an applied stash: every path stash@{index} touches goes back to its HEAD state, and the entry itself is left alone.

This is a path-scoped revert, not a diff-aware one — edits made on top of the applied stash are discarded together with it. That’s the deliberate trade for GitModule::stash_apply’s clean-tree precondition: within that contract, “back to HEAD” and “undo the apply” are the same thing.

Source

pub async fn stash_finalize( &self, working_dir: &Path, index: usize, expected_sha: Option<String>, ) -> Result<StashFinalizeOutput>

Drop stash@{index} once its content has been verified.

The sha is read before the drop and returned as StashFinalizeOutput::dropped_sha: the stash commit stays reachable until git gc prunes it, so GitModule::stash_restore can put the entry back. Callers should record it — that record is the difference between “dropped” and “lost”.

Source

pub async fn stash_restore( &self, working_dir: &Path, sha: &str, message: Option<String>, ) -> Result<StashRestoreOutput>

Put a dropped stash commit back on refs/stash.

sha is the StashFinalizeOutput::dropped_sha of an earlier finalize (or any stash commit sha). Dropping only removes the reflog entry — the commit itself lingers, unreferenced, until git gc prunes it, and this call re-references it.

Guarded so refs/stash cannot be turned into a dumping ground:

  1. sha must be >= 7 hex chars. Revspecs (HEAD, branch names, HEAD@{1}) are rejected — restoring is only ever about an object id somebody wrote down.
  2. The object must resolve. When it doesn’t, git gc is the likely reason and the error says so.
  3. The commit must be stash-shaped (>= 2 parents). Storing an ordinary commit would produce an entry whose “apply” means something nobody intended.
  4. The entry must not already be in the list — a second reference to the same content is a footgun, not a restore.

message overrides the reflog message; when omitted the stash commit’s own summary is reused, which is what the entry was called before it was dropped.

Source§

impl GitModule

Source

pub async fn worktree_add( &mut self, name: &str, branch: &str, base_branch: Option<&str>, ) -> Result<WorktreeAddOutput>

Create a new worktree at <worktrees_dir>/<name> on a new branch. The worktrees dir is the session-scoped root returned by [Session::worktrees_dir]; see [SessionConfig::worktrees_dir] for the resolution precedence and setup expectation.

Source

pub async fn worktree_remove( &mut self, name: &str, ) -> Result<WorktreeRemoveOutput>

Remove a session-owned worktree (force-removed, then forgotten).

Source

pub async fn commit( &self, working_dir: &Path, message: &str, only: Option<&[String]>, other_staged: OtherStagedMode, force_dot: bool, ) -> Result<CommitOutput>

Stage and commit changes in working_dir.

  • only == None (or an empty slice): sweeps every change with git add -A and commits. other_staged is ignored. Kept as the backward-compatible default so pre-existing callers see identical behaviour.
  • only == Some(paths): commits exactly paths. When the index already carries other staged work, the call is transactional under other_staged:
    • Stop — return an error, leave the index untouched. Safe default, forces the caller to reconcile explicitly.
    • Restage — unstage the intruding paths, stage + commit only, then re-stage the intruders. The resulting commit contains exactly only; the pre-existing staged work stays in the index.

Untracked files listed in only are staged and committed like any other path; anything not in only is never touched.

Dotfile / dot-dir safeguard. Any candidate whose path contains a .-prefixed component (.env, .claude/CLAUDE.md, .github/workflows/ci.yml, foo/.hidden) is classified before staging:

  • tracked → committed as usual, but recorded in CommitOutput::dotfile_warnings so pre-publish review can catch unintended edits to .gitignore / workflow files / etc.
  • untracked, not in .gitignore → dropped from staging + recorded in both dotfile_warnings and CommitOutput::dotfile_skipped.
  • untracked, in .gitignore → silently skipped (matches git’s default; git status --porcelain doesn’t surface them either).
  • force_dot=true → suppresses the entire mechanism: every candidate is staged verbatim and no warnings are emitted.
Source

pub async fn merge( &self, branch: &str, into_branch: &str, working_dir: &Path, ) -> Result<MergeOutput>

Merge branch into into_branch via --no-ff. On failure, the merge is auto-aborted and the original error surfaces.

Source

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

Delete a session-owned branch (refuses to delete unmerged work; use git branch -D directly if you really need that).

Source§

impl GitModule

Source

pub fn new(session: Arc<Session>) -> Self

Source

pub fn register_worktree(&mut self, path: PathBuf)

Source

pub fn is_owned(&self, path: &PathBuf) -> bool

Source

pub fn ensure_owned(&self, path: &PathBuf) -> Result<()>

Trait Implementations§

Source§

impl Debug for GitModule

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

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