Skip to content
Draft
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
40 changes: 18 additions & 22 deletions crates/but-api/src/commands/stack.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::commands::stack::create_reference::Anchor;
use crate::{App, error::Error};
use anyhow::{Context, anyhow};
use but_settings::AppSettings;
use but_workspace::branch::{ReferenceAnchor, ReferencePosition};
use gitbutler_branch_actions::internal::PushResult;
use gitbutler_branch_actions::stack::CreateSeriesRequest;
Expand Down Expand Up @@ -57,9 +56,9 @@ pub mod create_reference {
}
}

pub fn create_reference(_app: &App, params: create_reference::Params) -> Result<(), Error> {
pub fn create_reference(app: &App, params: create_reference::Params) -> Result<(), Error> {
let project = gitbutler_project::get(params.project_id)?;
let ctx = CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
let ctx = CommandContext::open(&project, app.app_settings.get()?.clone())?;
let create_reference::Request { new_name, anchor } = params.request;
let new_ref = Category::LocalBranch
.to_full_name(new_name.as_str())
Expand Down Expand Up @@ -101,10 +100,10 @@ pub fn create_reference(_app: &App, params: create_reference::Params) -> Result<
Ok(())
}

pub fn create_branch(_app: &App, params: CreateBranchParams) -> Result<(), Error> {
pub fn create_branch(app: &App, params: CreateBranchParams) -> Result<(), Error> {
let project = gitbutler_project::get(params.project_id)?;
let ctx = CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
if ctx.app_settings().feature_flags.ws3 {
let ctx = CommandContext::open(&project, app.app_settings.get()?.clone())?;
if app.app_settings.get()?.feature_flags.ws3 {
use ReferencePosition::Above;
let mut guard = project.exclusive_worktree_access();
let (repo, mut meta, graph) = ctx.graph_and_meta_mut_and_repo(guard.write_permission())?;
Expand Down Expand Up @@ -165,11 +164,11 @@ pub struct RemoveBranchParams {
pub branch_name: String,
}

pub fn remove_branch(_app: &App, params: RemoveBranchParams) -> Result<(), Error> {
pub fn remove_branch(app: &App, params: RemoveBranchParams) -> Result<(), Error> {
let project = gitbutler_project::get(params.project_id)?;
let ctx = CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
let ctx = CommandContext::open(&project, app.app_settings.get()?.clone())?;
let mut guard = project.exclusive_worktree_access();
if ctx.app_settings().feature_flags.ws3 {
if app.app_settings.get()?.feature_flags.ws3 {
let (repo, mut meta, graph) = ctx.graph_and_meta_mut_and_repo(guard.write_permission())?;
let ws = graph.to_workspace()?;
let ref_name = Category::LocalBranch
Expand Down Expand Up @@ -205,9 +204,9 @@ pub struct UpdateBranchNameParams {
pub new_name: String,
}

pub fn update_branch_name(_app: &App, params: UpdateBranchNameParams) -> Result<(), Error> {
pub fn update_branch_name(app: &App, params: UpdateBranchNameParams) -> Result<(), Error> {
let project = gitbutler_project::get(params.project_id)?;
let ctx = CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
let ctx = CommandContext::open(&project, app.app_settings.get()?.clone())?;
gitbutler_branch_actions::stack::update_branch_name(
&ctx,
params.stack_id,
Expand All @@ -227,11 +226,11 @@ pub struct UpdateBranchDescriptionParams {
}

pub fn update_branch_description(
_app: &App,
app: &App,
params: UpdateBranchDescriptionParams,
) -> Result<(), Error> {
let project = gitbutler_project::get(params.project_id)?;
let ctx = CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
let ctx = CommandContext::open(&project, app.app_settings.get()?.clone())?;
gitbutler_branch_actions::stack::update_branch_description(
&ctx,
params.stack_id,
Expand All @@ -250,12 +249,9 @@ pub struct UpdateBranchPrNumberParams {
pub pr_number: Option<usize>,
}

pub fn update_branch_pr_number(
_app: &App,
params: UpdateBranchPrNumberParams,
) -> Result<(), Error> {
pub fn update_branch_pr_number(app: &App, params: UpdateBranchPrNumberParams) -> Result<(), Error> {
let project = gitbutler_project::get(params.project_id)?;
let ctx = CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
let ctx = CommandContext::open(&project, app.app_settings.get()?.clone())?;
gitbutler_branch_actions::stack::update_branch_pr_number(
&ctx,
params.stack_id,
Expand All @@ -275,9 +271,9 @@ pub struct PushStackParams {
pub branch: String,
}

pub fn push_stack(_app: &App, params: PushStackParams) -> Result<PushResult, Error> {
pub fn push_stack(app: &App, params: PushStackParams) -> Result<PushResult, Error> {
let project = gitbutler_project::get(params.project_id)?;
let ctx = CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
let ctx = CommandContext::open(&project, app.app_settings.get()?.clone())?;
gitbutler_branch_actions::stack::push_stack(
&ctx,
params.stack_id,
Expand All @@ -297,9 +293,9 @@ pub struct PushStackToReviewParams {
pub user: User,
}

pub fn push_stack_to_review(_app: &App, params: PushStackToReviewParams) -> Result<String, Error> {
pub fn push_stack_to_review(app: &App, params: PushStackToReviewParams) -> Result<String, Error> {
let project = gitbutler_project::get(params.project_id)?;
let ctx = CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
let ctx = CommandContext::open(&project, app.app_settings.get()?.clone())?;
let review_id = gitbutler_sync::stack_upload::push_stack_to_review(
&ctx,
&params.user,
Expand Down
6 changes: 3 additions & 3 deletions crates/but-api/tests/api/commands/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ mod create_reference {
use but_api::commands::stack::create_reference;
use but_api::commands::stack::create_reference::{Params, Request};
use but_api::hex_hash::HexHash;
use but_workspace::branch::ReferencePosition;
use but_workspace::branch::create_reference::Position;
use gitbutler_project::ProjectId;
use std::str::FromStr;

Expand Down Expand Up @@ -34,7 +34,7 @@ mod create_reference {
gix::ObjectId::from_str("5c69907b1244089142905dba380371728e2e8160")
.unwrap(),
),
position: ReferencePosition::Above,
position: Position::Above,
}),
},
};
Expand All @@ -60,7 +60,7 @@ mod create_reference {
new_name: "new-branch".to_string(),
anchor: Some(create_reference::Anchor::AtReference {
short_name: "anchor-ref".into(),
position: ReferencePosition::Above,
position: Position::Above,
}),
},
};
Expand Down
33 changes: 21 additions & 12 deletions crates/but-graph/src/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ mod remotes;
mod overlay;
mod post;

pub(crate) type Entrypoint = Option<(gix::ObjectId, Option<gix::refs::FullName>)>;

/// A way to define information to be served from memory, instead of from the underlying data source, when
/// [initializing](Graph::from_commit_traversal()) the graph.
#[derive(Debug, Default)]
pub struct Overlay {
entrypoint: Entrypoint,
nonoverriding_references: Vec<gix::refs::Reference>,
meta_branches: Vec<(gix::refs::FullName, ref_metadata::Branch)>,
workspace: Option<(gix::refs::FullName, ref_metadata::Workspace)>,
Expand Down Expand Up @@ -141,7 +144,7 @@ impl Graph {
let mut graph = Graph::default();
// It's OK to default-initialise this here as overlays are only used when redoing
// the traversal.
let (_repo, meta) = Overlay::default().into_parts(repo, meta);
let (_repo, meta, _entrypoint) = Overlay::default().into_parts(repo, meta);
graph.insert_root(branch_segment_from_name_and_meta(
Some((ref_name, None)),
&meta,
Expand Down Expand Up @@ -228,7 +231,7 @@ impl Graph {
meta: &impl RefMetadata,
options: Options,
) -> anyhow::Result<Self> {
let (repo, meta) = Overlay::default().into_parts(tip.repo, meta);
let (repo, meta, _entrypoint) = Overlay::default().into_parts(tip.repo, meta);
Graph::from_commit_traversal_inner(tip.detach(), &repo, ref_name, &meta, options)
}

Expand Down Expand Up @@ -657,16 +660,22 @@ impl Graph {
meta: &impl RefMetadata,
overlay: Overlay,
) -> anyhow::Result<Self> {
let (repo, meta) = overlay.into_parts(repo, meta);
let tip_sidx = self
.entrypoint
.context("BUG: entrypoint must always be set")?
.0;
let tip = self
.tip_skip_empty(tip_sidx)
.context("BUG: entrypoint must eventually point to a commit")?
.id;
let ref_name = self[tip_sidx].ref_name.clone();
let (repo, meta, entrypoint) = overlay.into_parts(repo, meta);
let (tip, ref_name) = match entrypoint {
Some(t) => t,
None => {
let tip_sidx = self
.entrypoint
.context("BUG: entrypoint must always be set")?
.0;
let tip = self
.tip_skip_empty(tip_sidx)
.context("BUG: entrypoint must eventually point to a commit")?
.id;
let ref_name = self[tip_sidx].ref_name.clone();
(tip, ref_name)
}
};
Graph::from_commit_traversal_inner(tip, &repo, ref_name, &meta, self.options.clone())
}

Expand Down
17 changes: 15 additions & 2 deletions crates/but-graph/src/init/overlay.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::init::Overlay;
use crate::init::walk::RefsById;
use crate::init::{Entrypoint, Overlay};
use crate::is_workspace_ref_name;
use but_core::{RefMetadata, ref_metadata};
use gix::prelude::ReferenceExt;
Expand All @@ -17,6 +17,17 @@ impl Overlay {
self
}

/// Override the starting position of the traversal by setting it to `id`,
/// and optionally, by providing the `ref_name` that points to `id`.
pub fn with_entrypoint(
mut self,
id: gix::ObjectId,
ref_name: Option<gix::refs::FullName>,
) -> Self {
self.entrypoint = Some((id, ref_name));
self
}

/// Serve the given `branches` metadata from memory, as if they would exist,
/// possibly overriding metadata of a ref that already exists.
pub fn with_branch_metadata_override(
Expand All @@ -43,14 +54,15 @@ impl Overlay {
self,
repo: &'repo gix::Repository,
meta: &'meta T,
) -> (OverlayRepo<'repo>, OverlayMetadata<'meta, T>)
) -> (OverlayRepo<'repo>, OverlayMetadata<'meta, T>, Entrypoint)
where
T: RefMetadata,
{
let Overlay {
nonoverriding_references,
meta_branches,
workspace,
entrypoint,
} = self;
(
OverlayRepo {
Expand All @@ -62,6 +74,7 @@ impl Overlay {
meta_branches,
workspace,
},
entrypoint,
)
}
}
Expand Down
44 changes: 41 additions & 3 deletions crates/but-graph/src/projection/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ pub type CommitOwnerIndexes = (usize, usize, CommitIndex);

/// Utilities
impl Workspace<'_> {
/// Return `true` if the workspace itself is where `HEAD` is pointing to.
/// If `false`, one of the stack-segments is checked out instead.
pub fn is_entrypoint(&self) -> bool {
self.stacks
.iter()
.all(|s| s.segments.iter().all(|s| !s.is_entrypoint))
}
/// Lookup a triple obtained by [`Self::find_owner_indexes_by_commit_id()`] or panic.
pub fn lookup_commit(&self, (stack_idx, seg_idx, cidx): CommitOwnerIndexes) -> &StackCommit {
&self.stacks[stack_idx].segments[seg_idx].commits[cidx]
Expand Down Expand Up @@ -149,6 +156,37 @@ impl Workspace<'_> {
})
}

/// Return `true` if `name` is contained in the workspace as segment.
pub fn refname_is_segment(&self, name: &gix::refs::FullNameRef) -> bool {
self.find_segment_and_stack_by_refname(name).is_some()
}

/// Return `true` if the entrypoint.
pub fn is_reachable_from_entrypoint(&self, name: &gix::refs::FullNameRef) -> bool {
if self.is_entrypoint() {
self.refname_is_segment(name)
} else {
let Some((stack, segment_idx)) = self.stacks.iter().find_map(|stack| {
stack
.segments
.iter()
.enumerate()
.find_map(|(idx, segment)| segment.is_entrypoint.then_some((stack, idx)))
}) else {
return false;
};
stack
.segments
.get(segment_idx..)
.into_iter()
.any(|segments| {
segments
.iter()
.any(|s| s.ref_name.as_ref().is_some_and(|rn| rn.as_ref() == name))
})
}
}

/// Try to find `name` in any named [`StackSegment`] and return it along with the stack containing it.
pub fn find_segment_and_stack_by_refname(
&self,
Expand Down Expand Up @@ -189,9 +227,9 @@ pub enum WorkspaceKind {
/// The name of the reference pointing to the workspace commit. Useful for deriving the workspace name.
ref_name: gix::refs::FullName,
},
/// Information for when a workspace reference was advanced by hand and does not point to a
/// managed workspace commit anymore.
/// That commit, however, is reachable by following the first parent from the workspace reference.
/// Information for when a workspace reference was *possibly* advanced by hand and does not point to a
/// managed workspace commit (anymore).
/// That workspace commit, may be reachable by following the first parent from the workspace reference.
///
/// Note that the stacks that follow *will* be in unusable if the workspace commit is in a segment below,
/// but typically is usable if there is just a single real stack, or any amount of virtual stacks below
Expand Down
8 changes: 8 additions & 0 deletions crates/but-graph/tests/fixtures/scenarios.sh
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,14 @@ mkdir ws
create_workspace_commit_once main
)

git init just-init-with-two-branches
(cd just-init-with-two-branches
commit init
git branch A
git branch B
git checkout -b gitbutler/workspace
)

git init just-init-with-branches
(cd just-init-with-branches
commit init && setup_target_to_match_main
Expand Down
52 changes: 52 additions & 0 deletions crates/but-graph/tests/graph/init/with_workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,58 @@ fn minimal_merge() -> anyhow::Result<()> {
Ok(())
}

#[test]
fn stack_configuration_is_respected_if_one_of_them_is_an_entrypoint() -> anyhow::Result<()> {
let (repo, mut meta) = read_only_in_memory_scenario("ws/just-init-with-two-branches")?;
insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @"* fafd9d0 (HEAD -> gitbutler/workspace, main, B, A) init");

add_stack_with_segments(&mut meta, 1, "A", StackState::InWorkspace, &[]);
add_stack_with_segments(&mut meta, 2, "B", StackState::InWorkspace, &[]);

let graph = Graph::from_head(
&repo,
&*meta,
standard_options_with_extra_target(&repo, "main"),
)?
.validated()?;
insta::assert_snapshot!(graph_tree(&graph), @r"
└── 👉📕►►►:0[0]:gitbutler/workspace
├── 📙►:2[1]:A
│ └── ►:1[2]:anon:
│ └── ·fafd9d0 (⌂|🏘️|1) ►main
└── 📙►:3[1]:B
└── →:1:
");
insta::assert_snapshot!(graph_workspace(&graph.to_workspace()?), @r"
📕🏘️⚠️:0:gitbutler/workspace <> ✓! on fafd9d0
├── ≡📙:3:B on fafd9d0
│ └── 📙:3:B
└── ≡📙:2:A on fafd9d0
└── 📙:2:A
");

let (id, ref_name) = id_at(&repo, "B");
let graph = Graph::from_commit_traversal(id, ref_name.clone(), &*meta, standard_options())?
.validated()?;
// TODO: it shouldn't create a dependent branch here, but instead see A as a stack.
// problem is that for stack creation, there is no candidate.
insta::assert_snapshot!(graph_tree(&graph), @r"
└── 📕►►►:1[0]:gitbutler/workspace
└── 👉📙►:0[1]:B
└── 📙►:2[2]:A
└── ·fafd9d0 (⌂|🏘️|1) ►main
");
insta::assert_snapshot!(graph_workspace(&graph.to_workspace()?), @r"
📕🏘️⚠️:1:gitbutler/workspace <> ✓!
└── ≡👉📙:0:B
├── 👉📙:0:B
└── 📙:2:A
└── ·fafd9d0 (🏘️) ►main
");

Ok(())
}

#[test]
fn just_init_with_branches() -> anyhow::Result<()> {
let (repo, mut meta) = read_only_in_memory_scenario("ws/just-init-with-branches")?;
Expand Down
Loading
Loading