Skip to main content

ManageBranch

Struct ManageBranch 

Source
pub struct ManageBranch<'a> { /* private fields */ }
Expand description

Operations on a single branch within a repository.

Implementations§

Source§

impl<'a> ManageBranch<'a>

Source

pub async fn open( store: Arc<dyn ObjectStore>, prefix: impl Into<String>, branch: impl Into<String>, prompter: &'a dyn Prompter, ) -> Result<Self, ManageError>

Open a branch handle, verifying it exists by listing <prefix>/refs/heads/<branch>/ (or refs/heads/<branch>/ when prefix is empty).

§Errors

Returns ManageError::InvalidBranch if branch fails gix-validate’s strict ref-name check. Returns ManageError::BranchNotFound when no objects exist under the branch prefix. Returns ManageError::Store for object-store failures.

Source

pub async fn delete(&self) -> Result<(), ManageError>

Delete every object under the branch’s prefix after a yes/no confirmation. Aborts (returns Ok(())) if the user answers no; the Cancelled variant is reserved for prompt I/O failures.

Refuses outright when a PROTECTED# marker is present under the branch prefix — the operator must run unprotect first. This mirrors the refusal the helper-protocol delete path (delete_remote_ref_under_lock) emits, so a git push :branch against a protected ref and a management-CLI delete-branch of the same ref fail the same way.

§Per-ref lock (#158)

After the operator confirms the prompt, delete-branch acquires the same <prefix>/<ref>/LOCK#.lock the helper-protocol push and delete paths take. The lock is held across the fresh re-list, the baseline tombstone write (#143), and the synchronous sweep. Without it a concurrent git push could land a new bundle after the post-prompt re-list, the sweep would delete only the stale snapshot, and the ref would survive with the just-pushed bundle even though delete-branch reported success.

Lock acquisition runs AFTER the prompt — the prompt is interactive and could block indefinitely, and holding the lock across user input would make every other writer wait on the operator’s keyboard. If the lock is contended at acquisition time the function returns ManageError::LockContended and makes no changes. Release failures are downgraded to a warn! because the lock’s TTL guarantees a stale lock is recovered by the next acquirer; matches the protocol-push pattern.

The prompt-display and protection-marker check use a first listing for accuracy of the displayed object count, then a second listing is taken under the lock immediately before the deletion loop. The fresh listing drives the sweep so that any concurrent push landing under the branch prefix during the prompt window — before the lock window opens — is caught and deleted rather than left as a zombie object (#139). The protection-marker check is re-evaluated on the fresh listing so a protect racing with the prompt is honoured (#131) — the post-prompt re-check is what closes the TOCTOU window between the initial marker check and the deletion loop. If the fresh listing is empty (a concurrent delete won the race) the function reports it and returns Ok(()) rather than silently claiming success.

NotFound errors observed during the sweep are tolerated — they mean a concurrent deleter swept the key first, which still satisfies the operator’s intent. Other per-key delete errors (Network, AccessDenied, …) are collected: the loop does NOT short-circuit, every remaining key is still attempted, and the function returns ManageError::PartialDelete with the exact list of keys that survived so a retry can converge (#122). A list-call failure still propagates immediately because there is nothing to recover — without a listing the sweep cannot proceed.

Packchain refs with a parseable chain.json skip immediate deletion of the baseline bundle (<full_at>.bundle): a baseline tombstone is written first and the bundle is left for gc sweep to reclaim after the grace window (#143). The synchronous sweep still removes chain.json, path-index.json, and any other residue. The deferral protects an in-flight fetcher that already read the prior chain.json from a BaselineMissing range-GET failure; a fresh reader sees the missing chain and the ref is gone from its perspective. Bundle-engine refs, refs with an unparseable chain, and any tombstone PUT failure fall through to immediate bundle deletion so the operator’s “ref is gone” intent is never blocked on the tombstone path.

§Errors

Returns ManageError::Protected if the branch carries a PROTECTED# marker (checked on both listings), ManageError::LockContended if another writer holds the per-ref lock at acquisition time, ManageError::Cancelled if the user cancels the prompt, ManageError::Io for prompt or write I/O failures, ManageError::Store if a list operation fails, or ManageError::PartialDelete when one or more per-key deletes fail with a non-NotFound error after every key in the fresh listing has been attempted.

Source

pub async fn protect(&self) -> Result<(), ManageError>

Mark the branch as protected by writing the PROTECTED# sentinel. Idempotent — overwrites any existing marker.

§Per-ref lock (#159)

protect acquires the same <prefix>/<ref>/LOCK#.lock the helper-protocol push, helper-protocol delete, and delete-branch take. Pre-#159, the push path’s pre-bundle is_protected check could race a concurrent protect: a force-push that observed no marker would still overwrite the bundle even if protect landed between the under-lock is_protected and the bundle upload — because protect was a lockless put_bytes. Taking the same lock serialises protection state changes against the writers that consult it, closing the entire write window rather than narrowing it to a second sample.

If the lock is contended (a push, delete, or compact holds it), protect returns ManageError::LockContended and makes no changes. Operators can retry. Stale-lock recovery is inherited from acquire_lock (a previous holder that crashed without releasing).

Re-lists the branch prefix under the lock so a concurrent delete-branch (or last-bundle removal) that landed between ManageBranch::open and the lock window is caught and the marker is NOT written for a non-existent branch (#137). Without this re-check the orphaned PROTECTED# would persist with no automated cleanup and would silently block a future recreation of the same branch from being force-pushed or deleted. The re-listing filters out stale lock keys and any pre-existing PROTECTED# marker so a branch whose only residue is operational metadata is treated as gone.

§Errors

Returns ManageError::BranchNotFound if the under-lock listing shows the branch was deleted concurrently. Returns ManageError::LockContended if another writer holds the per-ref lock at acquisition time. Returns ManageError::Store if a list or put operation fails.

Source

pub async fn unprotect(&self) -> Result<(), ManageError>

Remove the PROTECTED# sentinel. A missing marker is treated as already-unprotected rather than an error.

§Per-ref lock (#159)

unprotect acquires the same per-ref lock as Self::protect so ALL protection state changes serialise against pushes, deletes, and compactions. Without taking the lock here a concurrent push observing is_protected() == true could otherwise commit to the protected refusal path just as unprotect landed, leaving the writer’s behaviour out of step with operator intent. Symmetry with protect keeps the lock the single point of serialisation for protection state.

§Errors

Returns ManageError::LockContended if another writer holds the per-ref lock at acquisition time. Returns ManageError::Store for object-store failures other than NotFound.

Auto Trait Implementations§

§

impl<'a> !RefUnwindSafe for ManageBranch<'a>

§

impl<'a> !UnwindSafe for ManageBranch<'a>

§

impl<'a> Freeze for ManageBranch<'a>

§

impl<'a> Send for ManageBranch<'a>

§

impl<'a> Sync for ManageBranch<'a>

§

impl<'a> Unpin for ManageBranch<'a>

§

impl<'a> UnsafeUnpin for ManageBranch<'a>

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

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<Unshared, Shared> IntoShared<Shared> for Unshared
where Shared: FromUnshared<Unshared>,

Source§

fn into_shared(self) -> Shared

Creates a shared type from an unshared type.
Source§

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

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
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 = 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<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

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