Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/but-api/src/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,7 @@ pub fn apply_only_with_perm(
workspace_reference_naming: WorkspaceReferenceNaming::default(),
order: None,
new_stack_id: None,
allow_applying_already_applied_branch_when_outside_workspace: false,
},
)?;

Expand Down
1 change: 1 addition & 0 deletions crates/but-debug/src/command/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub(crate) fn apply(
workspace_reference_naming: WorkspaceReferenceNaming::Default,
order: None,
new_stack_id: None,
..Default::default()
},
)?;

Expand Down
77 changes: 49 additions & 28 deletions crates/but-workspace/src/branch/apply.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
use anyhow::{Context as _, bail};
use bstr::ByteSlice as _;
use but_core::{
ObjectStorageExt, RefMetadata, RepositoryExt, extract_remote_name_and_short_name, ref_metadata,
ref_metadata::{
Workspace,
WorkspaceCommitRelation::{Merged, Outside},
},
};
use but_core::{
WORKSPACE_REF_NAME,
ref_metadata::{StackId, StackKind},
};
use but_graph::{SegmentIndex, init::Overlay, petgraph::Direction, workspace::WorkspaceKind};
use gix::{
prelude::ObjectIdExt,
reference::Category,
refs::{
FullNameRef, Target,
transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog},
},
};
use tracing::instrument;

use crate::branch::{OnWorkspaceMergeConflict, try_find_validated_ref};
use crate::{
WorkspaceCommit,
branch::{anon_stacks, ensure_no_missing_stacks},
commit::merge::Tip,
ref_info::WorkspaceExt,
};

/// A stack that conflicted while applying a branch.
#[derive(Clone)]
Expand Down Expand Up @@ -167,34 +191,20 @@ pub struct Options {
pub order: Option<usize>,
/// Create new stack id, which by default is a function that generates a new StackId.
pub new_stack_id: Option<fn(&gix::refs::FullNameRef) -> StackId>,
/// By default applying branches that are already applied is considered an error. Setting this
/// to `true` changes that so if we're not in a workspace, applying already applied branches is
/// allowed.
///
/// The apply follows the normal apply path, as if the branch wasn't applied.
///
/// The use case for this is to go from single branch mode to workspace mode. If we're in SBM
/// with branch `foo` applied and we apply `foo` with
/// `allow_applying_already_applied_branch_when_outside_workspace` set to `true`, we'll be put
/// into a workspace with only `foo` applied, regardless which branches were previously
/// applied.
pub allow_applying_already_applied_branch_when_outside_workspace: bool,
Comment thread
davidpdrsn marked this conversation as resolved.
}

use anyhow::{Context as _, bail};
use but_core::{
ObjectStorageExt, RefMetadata, RepositoryExt, extract_remote_name_and_short_name, ref_metadata,
ref_metadata::{
Workspace,
WorkspaceCommitRelation::{Merged, Outside},
},
};
use but_graph::{SegmentIndex, init::Overlay, petgraph::Direction, workspace::WorkspaceKind};
use gix::{
prelude::ObjectIdExt,
reference::Category,
refs::{
FullNameRef, Target,
transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog},
},
};
use tracing::instrument;

use crate::{
WorkspaceCommit,
branch::{anon_stacks, ensure_no_missing_stacks},
commit::merge::Tip,
ref_info::WorkspaceExt,
};

/// Apply `branch` to the given `workspace`, and possibly create the workspace reference in `repo`
/// along with its `meta`-data if it doesn't exist yet.
/// The changed workspace will be checked out.
Expand Down Expand Up @@ -228,6 +238,7 @@ pub fn apply(
workspace_reference_naming,
order,
new_stack_id,
allow_applying_already_applied_branch_when_outside_workspace,
}: Options,
) -> anyhow::Result<Outcome> {
let ws = workspace;
Expand Down Expand Up @@ -256,11 +267,22 @@ pub fn apply(
branch = upstream_branch_name;
branch_ref = try_find_validated_ref(repo, branch.as_ref(), "apply")?;
}
let head_ref_name = repo.head_name()?.map(|name| name.to_owned());
let head_on_managed_workspace_ref = match &ws.kind {
WorkspaceKind::Managed { ref_info }
| WorkspaceKind::ManagedMissingWorkspaceCommit { ref_info } => head_ref_name
.as_ref()
.is_some_and(|head| head.as_ref() == ref_info.ref_name.as_ref()),
WorkspaceKind::AdHoc => false,
};
let branch_has_applied_metadata =
branch_has_applied_workspace_metadata(branch.as_ref(), &ws, meta)?;
let branch_already_applied =
ws.is_reachable_from_entrypoint(branch.as_ref()) && branch_has_applied_metadata;
if branch_already_applied {
if branch_already_applied
&& (!allow_applying_already_applied_branch_when_outside_workspace
|| head_on_managed_workspace_ref)
{
let workspace_ref_created = false;
// When exiting early, don't try to adjust the ws commit.
return Ok(Outcome {
Expand Down Expand Up @@ -382,7 +404,6 @@ pub fn apply(
};
// Whether HEAD already points at the workspace ref, or sits directly on a branch. When it's on a
// branch we move HEAD onto the workspace ref and rebuild the workspace around the branches we keep.
let head_ref_name = repo.head_name()?.map(|rn| rn.to_owned());
let head_on_workspace_ref = head_ref_name
.as_ref()
.is_some_and(|head| head.as_bstr() == workspace_ref_name_to_update.as_bstr());
Expand Down
56 changes: 43 additions & 13 deletions crates/but-workspace/src/branch/create_reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,30 +330,29 @@ pub(super) mod function {

AnchorResolution::positioned(ref_target_id, validate_id, instruction)
}
Some(Anchor::AtSegment { ref_name, position }) => {
Some(Anchor::AtSegment {
ref_name: anchor_ref,
position,
}) => {
let mut validate_id = true;
let ref_target_id = if workspace.has_metadata() {
let (stack_idx, seg_idx) =
workspace.try_find_segment_owner_indexes_by_refname(ref_name.as_ref())?;
workspace.try_find_segment_owner_indexes_by_refname(anchor_ref.as_ref())?;
let segment = &workspace.stacks[stack_idx].segments[seg_idx];

let id = workspace
workspace
.tip_commit_by_segment_id(segment.id)
.map(|commit| position.resolve_commit(commit.into(), ws_base))
.context(
"BUG: we should always see through to the base or eligible commits",
)??;
if Some(id) == ws_base {
validate_id = false
}
id
)??
} else {
let Some((_stack, segment)) =
workspace.find_segment_and_stack_by_refname(ref_name.as_ref())
workspace.find_segment_and_stack_by_refname(anchor_ref.as_ref())
else {
bail!(
"Could not find a segment named '{}' in workspace",
ref_name.shorten()
anchor_ref.shorten()
);
};
position.resolve_commit(
Expand All @@ -365,11 +364,42 @@ pub(super) mod function {
ws_base,
)?
};
AnchorResolution::positioned(
let points_to_workspace_base = Some(ref_target_id) == ws_base;
if points_to_workspace_base {
validate_id = false;
}
// The lower bound owns no commits, so an ad-hoc workspace needs explicit ref
// ordering to project the new empty segment at that boundary.
let branch_stack_order = if !workspace.has_metadata() && points_to_workspace_base {
if !meta.can_persist_branch_stack_order() {
bail_precondition!(
"Cannot position '{new}' relative to local reference '{anchor}' at the workspace base without branch order metadata",
new = ref_name.shorten(),
anchor = anchor_ref.shorten()
);
}
let existing_order = meta
.branch_stack_order(anchor_ref.as_ref())?
.unwrap_or_default();
Some(insert_into_branch_stack_order(
existing_order,
anchor_ref.as_ref(),
ref_name,
position,
))
} else {
None
};
let mut resolution = AnchorResolution::positioned(
ref_target_id,
validate_id,
Some(Instruction::Dependent { ref_name, position }),
)
Some(Instruction::Dependent {
ref_name: anchor_ref,
position,
}),
);
resolution.branch_stack_order = branch_stack_order;
resolution
}
// Position relative to another *reference* on the same commit. Managed workspaces
// order these in workspace metadata; ad-hoc workspaces record the order in the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6226,6 +6226,7 @@ fn apply_options() -> but_workspace::branch::apply::Options {
workspace_reference_naming: WorkspaceReferenceNaming::Default,
order: None,
new_stack_id: Some(stack_id_for_name),
allow_applying_already_applied_branch_when_outside_workspace: false,
}
}

Expand Down
Loading
Loading