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
111 changes: 55 additions & 56 deletions Cargo.lock

Large diffs are not rendered by default.

17 changes: 15 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ gitbutler-git = { path = "crates/gitbutler-git" }
gitbutler-watcher = { path = "crates/gitbutler-watcher" }
gitbutler-filemonitor = { path = "crates/gitbutler-filemonitor" }
gitbutler-testsupport = { path = "crates/gitbutler-testsupport" }
gitbutler-cli = { path = "crates/gitbutler-cli" }
gitbutler-branch-actions = { path = "crates/gitbutler-branch-actions" }
gitbutler-sync = { path = "crates/gitbutler-sync" }
gitbutler-oplog = { path = "crates/gitbutler-oplog" }
Expand Down Expand Up @@ -71,7 +70,6 @@ gitbutler-hunk-dependency = { path = "crates/gitbutler-hunk-dependency" }
but-settings = { path = "crates/but-settings" }
gitbutler-workspace = { path = "crates/gitbutler-workspace" }
but = { path = "crates/but" }
but-server = { path = "crates/but-server" }
but-testsupport = { path = "crates/but-testsupport" }
but-rebase = { path = "crates/but-rebase" }
but-core = { path = "crates/but-core" }
Expand Down Expand Up @@ -112,3 +110,18 @@ incremental = false

[profile.test]
incremental = false

# Assure that `gix` is always fast so debug builds aren't unnecessarily slow.
[profile.dev.package]
gix-object = { opt-level = 3 }
gix-ref = { opt-level = 3 }
gix-pack = { opt-level = 3 }
gix-hash = { opt-level = 3 }
gix-actor = { opt-level = 3 }
gix-config = { opt-level = 3 }
sha1-checked = { opt-level = 3 }
zlib-rs = { opt-level = 3 }
# This one is special as we can run into debug assertions otherwise.
# They have a time, but let's chose the time instead of always being hit by it.
gix-merge = { opt-level = 3, debug-assertions = false }

4 changes: 2 additions & 2 deletions crates/but-graph/src/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ impl Graph {
}
gix::head::Kind::Symbolic(existing_reference) => {
let mut existing_reference = existing_reference.attach(repo);
let tip = existing_reference.peel_to_id_in_place()?;
let tip = existing_reference.peel_to_id()?;
(tip, Some(existing_reference.inner.name))
}
};
Expand Down Expand Up @@ -487,7 +487,7 @@ impl Graph {
{
let Some(segment_tip) = repo
.try_find_reference(segment.ref_name.as_ref())?
.map(|mut r| r.peel_to_id_in_place())
.map(|mut r| r.peel_to_id())
.transpose()?
else {
continue;
Expand Down
2 changes: 1 addition & 1 deletion crates/but-graph/src/init/walk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ pub fn try_refname_to_id(
) -> anyhow::Result<Option<gix::ObjectId>> {
Ok(repo
.try_find_reference(refname)?
.map(|mut r| r.peel_to_id_in_place())
.map(|mut r| r.peel_to_id())
.transpose()?
.map(|id| id.detach()))
}
Expand Down
37 changes: 27 additions & 10 deletions crates/but-graph/src/projection/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::projection::workspace;
use crate::{
CommitFlags, CommitIndex, Graph, Segment, SegmentIndex,
projection::{Stack, StackCommit, StackCommitFlags, StackSegment},
segment,
};
use anyhow::Context;
use bstr::{BStr, ByteSlice};
Expand Down Expand Up @@ -68,6 +69,17 @@ impl Workspace<'_> {
.iter()
.all(|s| s.segments.iter().all(|s| !s.is_entrypoint))
}

/// Return the `commit` at the tip of the workspace itself, and do so by following empty segments along the
/// first parent until the first commit is found.
/// This importantly is different from the [`Graph::lookup_entrypoint()`] `commit`, as the entrypoint could be anywhere
/// inside the workspace as well.
///
/// Note that this commit could also be the base of the workspace, particularly if there is no commits in the workspace.
pub fn tip_commit(&self) -> Option<&segment::Commit> {
self.graph.tip_skip_empty(self.id)
}

/// 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 @@ -161,23 +173,28 @@ impl Workspace<'_> {
self.find_segment_and_stack_by_refname(name).is_some()
}

/// Return `true` if the entrypoint.
/// Return `true` if `name` is in the ancestry of the workspace entrypoint, and is IN the workspace as well.
pub fn is_reachable_from_entrypoint(&self, name: &gix::refs::FullNameRef) -> bool {
if self.ref_name().filter(|_| self.is_entrypoint()) == Some(name) {
return true;
}
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 {
let Some((entrypoint_stack, entrypoint_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
entrypoint_stack
.segments
.get(segment_idx..)
.get(entrypoint_segment_idx..)
.into_iter()
.any(|segments| {
segments
Expand Down
2 changes: 1 addition & 1 deletion crates/but-graph/src/ref_metadata_legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl Snapshot {
let Ok(mut r) = repo.find_reference(&segment.name) else {
continue;
};
if let Ok(id) = r.peel_to_id_in_place() {
if let Ok(id) = r.peel_to_id() {
segment.head = CommitOrChangeId::CommitId(id.to_string());
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/but-rebase/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! An API for an interactive rebases, suitable for interactive, UI driven, and programmatic use.
//!
//! It will only affect the commit-graph, and never the alter the worktree in any way.
#![deny(rust_2018_idioms, missing_docs)]
#![deny(missing_docs)]

use crate::commit::DateMode;
use anyhow::{Context, Ok, Result, anyhow, bail};
Expand Down
2 changes: 1 addition & 1 deletion crates/but-testing/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ pub fn graph(
None => but_graph::Graph::from_head(&repo, &*meta, opts),
Some(ref_name) => {
let mut reference = repo.find_reference(ref_name)?;
let id = reference.peel_to_id_in_place()?;
let id = reference.peel_to_id()?;
but_graph::Graph::from_commit_traversal(id, reference.name().to_owned(), &*meta, opts)
}
}?;
Expand Down
4 changes: 2 additions & 2 deletions crates/but-testsupport/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Utilities for testing.
#![deny(rust_2018_idioms, missing_docs)]
#![deny(missing_docs)]

use gix::Repository;
use gix::bstr::{BStr, ByteSlice};
Expand Down Expand Up @@ -252,7 +252,7 @@ pub fn id_at<'repo>(repo: &'repo Repository, name: &str) -> (gix::Id<'repo>, gix
let mut rn = repo
.find_reference(name)
.expect("statically known reference exists");
let id = rn.peel_to_id_in_place().expect("must be valid reference");
let id = rn.peel_to_id().expect("must be valid reference");
(id, rn.inner.name)
}

Expand Down
1 change: 0 additions & 1 deletion crates/but-workspace/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ flume = "0.11.1"

[dev-dependencies]
but-testsupport.workspace = true
pretty_assertions = "1.4.1"
insta = "1.43.1"
but-core = { workspace = true, features = ["testing"] }
# for stable hashes in `gitbuter-` crates while we use them.
Expand Down
86 changes: 66 additions & 20 deletions crates/but-workspace/src/branch/apply.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::branch::checkout::UncommitedWorktreeChanges;
use std::borrow::Cow;

/// Returned by [function::apply()].
Expand Down Expand Up @@ -39,7 +40,7 @@ pub enum IntegrationMode {
MergeIfNeeded,
}

/// What to do if the applied branch conflicts?
/// What to do if the applied branch conflicts with the existing branches?
#[derive(Default, Debug, Copy, Clone)]
pub enum OnWorkspaceConflict {
/// Provide additional information about the stack that conflicted and the files involved in it,
Expand All @@ -63,18 +64,26 @@ pub enum WorkspaceReferenceNaming {
pub struct Options {
/// how the branch should be brought into the workspace.
pub integration_mode: IntegrationMode,
/// Decide how to deal with conflicts.
/// Decide how to deal with conflicts when creating the workspace merge commit to bring in each stack.
pub on_workspace_conflict: OnWorkspaceConflict,
/// How the workspace reference should be named should it be created.
/// The creation is always needed if there are more than one branch applied.
pub workspace_reference_naming: WorkspaceReferenceNaming,
/// How the worktree checkout should behave int eh light of uncommitted changes in the worktree.
pub uncommitted_changes: UncommitedWorktreeChanges,
/// If not `None`, the applied branch should be merged into the workspace commit at the N'th parent position.
/// This is useful if the tip of a branc (at a specific position) was unapplied, and a segment within that branch
/// should now be re-applied, but of course, be placed at the same spot and not end up at the end of the workspace.
pub order: Option<usize>,
}

pub(crate) mod function {
use super::{Options, Outcome, WorkspaceReferenceNaming};
use crate::branch::checkout;
use crate::ref_info::WorkspaceExt;
use anyhow::bail;
use anyhow::{Context, bail};
use but_core::RefMetadata;
use but_graph::init::Overlay;
use but_graph::projection::WorkspaceKind;
use std::borrow::Cow;

Expand All @@ -91,33 +100,64 @@ pub(crate) mod function {
///
/// On `error`, neither `repo` nor `meta` will have been changed, but `repo` may contain in-memory objects.
/// Otherwise, objects will have been persisted, and references and metadata will have been updated.
pub fn apply<'graph, T: RefMetadata>(
pub fn apply<'graph>(
branch: &gix::refs::FullNameRef,
workspace: &but_graph::projection::Workspace<'graph>,
repo: &mut gix::Repository,
_meta: &mut T,
meta: &mut impl RefMetadata,
Options {
integration_mode: _,
on_workspace_conflict: _,
workspace_reference_naming,
uncommitted_changes,
order: _to_be_used_in_merge,
}: Options,
) -> anyhow::Result<Outcome<'graph>> {
if repo
.try_find_reference(branch)?
.is_some_and(|r| matches!(r.target(), gix::refs::TargetRef::Symbolic(_)))
{
bail!(
"Refusing to apply symbolic ref '{}' due to potential ambiguity",
branch.shorten()
);
}
if workspace.is_reachable_from_entrypoint(branch) {
if workspace.is_entrypoint()
|| workspace
.stacks
.iter()
.flat_map(|s| s.segments.iter().filter_map(|s| s.ref_name.as_ref()))
.any(|rn| rn.as_ref() == branch)
{
return Ok(Outcome {
graph: Cow::Borrowed(workspace.graph),
workspace_ref_created: false,
});
}
return Ok(Outcome {
graph: Cow::Borrowed(workspace.graph),
workspace_ref_created: false,
});
} else if workspace.refname_is_segment(branch) {
todo!("checkout workspace so the to-be-applied branch becomes visible")
}
// This means our workspace encloses the desired branch, but it's not checked out yet.
let commit_to_checkout = workspace
.tip_commit()
.context("Workspace must point to a commit to check out")?;
let current_head_commit = workspace
.graph
.lookup_entrypoint()?
.commit
.context("The entrypoint must have a commit - it's equal to HEAD")?;
crate::branch::safe_checkout(
current_head_commit.id,
commit_to_checkout.id,
repo,
checkout::Options {
uncommitted_changes,
},
)?;
let graph = workspace.graph.redo_traversal_with_overlay(
repo,
meta,
Overlay::default().with_entrypoint(
commit_to_checkout.id,
workspace.ref_name().map(|rn| rn.to_owned()),
),
)?;
return Ok(Outcome {
graph: Cow::Owned(graph),
workspace_ref_created: false,
});
};

if let Some(ws_ref_name) = workspace.ref_name()
&& repo.try_find_reference(ws_ref_name)?.is_none()
Expand All @@ -133,6 +173,12 @@ pub(crate) mod function {
bail!("Refusing to work on workspace whose workspace commit isn't at the top");
}

if meta.workspace_opt(branch)?.is_some() {
bail!(
"Refusing to apply a reference that already is a workspace: '{}'",
branch.shorten()
);
}
// In general, we only have to deal with one branch to apply. But when we are on an adhoc workspace,
// we need to assure both branches go into the existing or the new workspace.
let (_workspace_ref_name_to_update, _branches_to_apply) = match &workspace.kind {
Expand Down Expand Up @@ -171,7 +217,7 @@ pub(crate) mod function {
// ws_id
// }
// Some(mut existing_workspace_reference) => {
// let id = existing_workspace_reference.peel_to_id_in_place()?;
// let id = existing_workspace_reference.peel_to_id()?;
// id.detach()
// }
// };
Expand Down
Loading
Loading