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
5 changes: 0 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"test:ui": "vitest --ui",
"build": "vite build",
"preview": "vite preview",
"package": "svelte-kit sync",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check-cycles": "tsx ../../scripts/check-component-cycles.ts",
"check:watch": "pnpm check --watch",
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/lib/testing/mockStackService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const MOCK_COMMIT_A: Commit = {
state: { type: "LocalOnly" },
createdAt: BigInt(1672531200000), // Example timestamp
author: MOCK_AUTHOR_A,
changeId: "Icommit-a-id",
gerritReviewUrl: null,
Comment thread
Byron marked this conversation as resolved.
};

Expand All @@ -26,6 +27,7 @@ const MOCK_UPSTREAM_COMMIT_A: UpstreamCommit = {
message: "Upstream commit message",
createdAt: BigInt(1672531200000), // Example timestamp
author: MOCK_AUTHOR_A,
changeId: null,
};

const BRANCH_DETAILS_A: BranchDetails = {
Expand Down
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dev": "vite dev --host 0.0.0.0",
"build": "vite build",
"preview": "vite preview",
"package": "svelte-kit sync",
"prepare": "svelte-kit sync",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
Expand Down
2 changes: 0 additions & 2 deletions crates/but-action/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,11 @@ fn default_target_setting_if_none(

fn stacks(ctx: &Context, repo: &gix::Repository) -> anyhow::Result<Vec<StackEntry>> {
let meta = ctx.legacy_meta()?;
let mut cache = ctx.cache.get_cache_mut()?;
but_workspace::legacy::stacks_v3(
repo,
&meta,
but_workspace::legacy::StacksFilter::InWorkspace,
None,
&mut cache,
)
}

Expand Down
3 changes: 1 addition & 2 deletions crates/but-action/src/reword.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,5 @@ pub fn commit(
fn stacks(ctx: &Context) -> anyhow::Result<Vec<StackEntry>> {
let repo = ctx.clone_repo_for_merging_non_persisting()?;
let meta = ctx.legacy_meta()?;
let mut cache = ctx.cache.get_cache_mut()?;
but_workspace::legacy::stacks_v3(&repo, &meta, StacksFilter::default(), None, &mut cache)
but_workspace::legacy::stacks_v3(&repo, &meta, StacksFilter::default(), None)
}
8 changes: 2 additions & 6 deletions crates/but-api/src/legacy/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,13 @@ mod json {
pub fn head_info(ctx: &but_ctx::Context) -> Result<but_workspace::RefInfo> {
let repo = ctx.clone_repo_for_merging_non_persisting()?;
let meta = ctx.meta()?;
let mut cache = ctx.cache.get_cache_mut()?;
but_workspace::head_info(
&repo,
&meta,
but_workspace::ref_info::Options {
traversal: but_graph::init::Options::limited(),
expensive_commit_info: true,
},
&mut cache,
)
.map(|info| info.pruned_to_entrypoint())
}
Expand All @@ -84,8 +82,7 @@ pub(crate) fn stacks_v3_from_ctx(
) -> anyhow::Result<Vec<but_workspace::legacy::ui::StackEntry>> {
let repo = ctx.clone_repo_for_merging_non_persisting()?;
let meta = ctx.meta()?;
let mut cache = ctx.cache.get_cache_mut()?;
but_workspace::legacy::stacks_v3(&repo, &meta, filter, None, &mut cache)
but_workspace::legacy::stacks_v3(&repo, &meta, filter, None)
}

#[cfg(unix)]
Expand Down Expand Up @@ -185,8 +182,7 @@ pub fn stack_details(
let mut details = {
let repo = ctx.clone_repo_for_merging_non_persisting()?;
let meta = ctx.meta()?;
let mut cache = ctx.cache.get_cache_mut()?;
but_workspace::legacy::stack_details_v3(stack_id, &repo, &meta, &mut cache)
but_workspace::legacy::stack_details_v3(stack_id, &repo, &meta)
}?;
let repo = ctx.repo.get()?;
let gerrit_mode = repo.git_settings()?.gitbutler_gerrit_mode.unwrap_or(false);
Expand Down
3 changes: 1 addition & 2 deletions crates/but-cherry-apply/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ pub fn cherry_apply_status(
.with_object_memory();

let meta = ctx.legacy_meta()?;
let mut cache = ctx.cache.get_cache_mut()?;
let stacks = stacks_v3(&repo, &meta, StacksFilter::InWorkspace, None, &mut cache)?;
let stacks = stacks_v3(&repo, &meta, StacksFilter::InWorkspace, None)?;

if stacks.is_empty() {
return Ok(CherryApplyStatus::NoStacks);
Expand Down
6 changes: 2 additions & 4 deletions crates/but-cherry-apply/tests/cherry_apply/clean_to_both.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ fn can_apply_to_foo_stack() -> anyhow::Result<()> {
.join("virtual_branches.toml"),
)?;
let repo = test_ctx.ctx.repo.get()?;
let mut cache = test_ctx.ctx.cache.get_cache_mut()?;
let details = stack_details_v3(Some(foo_id), &repo, &meta, &mut cache)?;
let details = stack_details_v3(Some(foo_id), &repo, &meta)?;

let has_commit = details
.branch_details
Expand Down Expand Up @@ -98,8 +97,7 @@ fn can_apply_to_bar_stack() -> anyhow::Result<()> {
.join("virtual_branches.toml"),
)?;
let repo = test_ctx.ctx.repo.get()?;
let mut cache = test_ctx.ctx.cache.get_cache_mut()?;
let details = stack_details_v3(Some(bar_id), &repo, &meta, &mut cache)?;
let details = stack_details_v3(Some(bar_id), &repo, &meta)?;

let has_commit = details
.branch_details
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ fn can_only_apply_to_bar_stack() -> anyhow::Result<()> {
.join("virtual_branches.toml"),
)?;
let repo = test_ctx.ctx.repo.get()?;
let mut cache = test_ctx.ctx.cache.get_cache_mut()?;
let details = stack_details_v3(Some(bar_id), &repo, &meta, &mut cache)?;
let details = stack_details_v3(Some(bar_id), &repo, &meta)?;

let has_commit = details
.branch_details
Expand Down
6 changes: 2 additions & 4 deletions crates/but-claude/src/hooks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,15 +597,13 @@ impl OutputClaudeJson for Result<ClaudeHookOutput> {
fn stack_details(ctx: &Context, stack_id: StackId) -> anyhow::Result<StackDetails> {
let repo = ctx.clone_repo_for_merging_non_persisting()?;
let meta = ctx.legacy_meta()?;
let mut cache = ctx.cache.get_cache_mut()?;
but_workspace::legacy::stack_details_v3(Some(stack_id), &repo, &meta, &mut cache)
but_workspace::legacy::stack_details_v3(Some(stack_id), &repo, &meta)
}

fn list_stacks(ctx: &Context) -> anyhow::Result<Vec<StackEntry>> {
let repo = ctx.repo.get()?;
let meta = ctx.legacy_meta()?;
let mut cache = ctx.cache.get_cache_mut()?;
but_workspace::legacy::stacks_v3(&repo, &meta, StacksFilter::default(), None, &mut cache)
but_workspace::legacy::stacks_v3(&repo, &meta, StacksFilter::default(), None)
}

/// Returns true if the session has `is_gui` set to true, and `GUTBUTLER_IN_GUI` is unset
Expand Down
2 changes: 1 addition & 1 deletion crates/but-core/src/change_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl ChangeId {
ChangeId(value.to_string().into())
}

/// Creates a random length 32 reverse hex ChangeId.
/// Creates a random length 32 reverse hex ChangeId (SHA-1).
pub fn generate() -> Self {
let mut rng = rand::rng();
let bytes: [u8; CHANGE_ID_REVERSE_BYTE_LEN] = rng.random();
Expand Down
85 changes: 72 additions & 13 deletions crates/but-core/src/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
};

/// A collection of all the extra information we keep in the headers of a commit.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Headers {
/// A property we can use to determine if two different commits are
/// actually the same "patch" at different points in time. We carry it
Expand All @@ -34,20 +34,53 @@ pub struct Headers {

/// Lifecycle
impl Headers {
/// Creates a new set of headers with a randomly generated change_id.
/// Derive a deterministic synthetic change-id from `commit_id`.
///
/// Useful for when [`Self::change_id`] is `None`.
///
/// # Note - use [Self::from_config()] instead
/// These synthesized IDs are compatible with Jujutsu's deterministic scheme,
/// and JJ would create exactly the same change-id if given the `commit_id`.
pub fn synthetic_change_id_from_commit_id(commit_id: gix::ObjectId) -> ChangeId {
let bytes: Vec<_> = commit_id.as_bytes()[4..gix::hash::Kind::Sha1.len_in_bytes()]
.iter()
.rev()
.map(|byte| byte.reverse_bits())
.collect();
ChangeId::from_bytes(&bytes)
}

/// Fill in [`Self::change_id`] with a deterministic synthetic value derived from `commit_id`
/// if it is not set yet, and return the updated headers.
/// If `commit_id` is `None`, this means no commit-id is known and we create a random ID (SHA1) instead.
///
/// Use this when headers already exist or are being built up incrementally, but a stored
/// `change-id` header still needs to be ensured.
pub fn ensure_change_id(mut self, commit_id: impl Into<Option<gix::ObjectId>>) -> Self {
if self.change_id.is_none() {
self.change_id = commit_id
.into()
.map_or_else(ChangeId::generate, Self::synthetic_change_id_from_commit_id)
.into();
}
self
}

/// Creates a new set of headers with a randomly generated change_id.
#[cfg(feature = "legacy")]
#[deprecated = "We want deterministic change-ids, use Headers::synthetic_change_id_from_commit_id() instead."]
pub fn new_with_random_change_id() -> Self {
Self {
change_id: Some(ChangeId::generate()),
conflicted: None,
}
}

/// Create a new instance, with the following rules for setting the change id:
/// Create a new instance, with the following rules for setting the change id header:
/// 1. Read `gitbutler.testing.changeId` from `config` and if it's a valid u128 integer, use it as change-id.
/// 2. generate a new change-id
///
/// This produces a stored header value. For the deterministic fallback used when headerless
/// commits still need a change-id, see [`Self::ensure_change_id()`].
pub fn from_config(config: &gix::config::Snapshot) -> Self {
Headers {
change_id: Some(
Expand All @@ -71,6 +104,10 @@ impl Headers {

/// Extract header information from the given [`extra_headers`](gix::objs::Commit::extra_headers()) function,
/// or return `None` if not present.
///
/// The `change-id` header takes precedence over the legacy `gitbutler-change-id` header.
/// If neither header is present and a stable fallback is required, use
/// `Headers::unwrap_or_default().ensure_change_id(commit_id)`
pub fn try_from_commit_headers<'a, I>(
extra_headers: impl Fn() -> gix::objs::commit::ExtraHeaders<I>,
) -> Option<Self>
Expand Down Expand Up @@ -110,8 +147,11 @@ impl Headers {
}
}

/// Write the values from this instance to the given `commit`, fully replacing any header
/// Write the values from this instance to the given `commit`, fully replacing any header
/// that might have been there before.
///
/// This always writes the canonical `change-id` header for [`Self::change_id`]. Use
/// [`Self::ensure_change_id()`] to persist a deterministic fallback,
pub fn set_in_commit(&self, commit: &mut gix::objs::Commit) {
Self::remove_in_commit(commit);
commit
Expand Down Expand Up @@ -417,7 +457,7 @@ impl TreeKind {
}
}

/// Instantiation
/// Lifecycle
impl<'repo> Commit<'repo> {
/// Decode the object at `commit_id` and keep its data for later query.
pub fn from_id(commit_id: gix::Id<'repo>) -> anyhow::Result<Self> {
Expand All @@ -444,13 +484,6 @@ impl From<Commit<'_>> for CommitOwned {
}
}

impl Commit<'_> {
/// Set this commit to use the given `headers`, completely replacing the ones it might currently have.
pub fn set_headers(&mut self, header: &Headers) {
header.set_in_commit(self)
}
}

impl std::ops::Deref for Commit<'_> {
type Target = gix::objs::Commit;

Expand Down Expand Up @@ -495,6 +528,23 @@ impl CommitOwned {
inner,
}
}

/// Return the stored change-id if present, or derive a deterministic fallback from the commit id.
pub fn change_id(&self) -> ChangeId {
Headers::try_from_commit(&self.inner)
.unwrap_or_default()
.ensure_change_id(self.id)
.change_id
.expect("change-id is ensured")
}
}

/// Mutations
impl Commit<'_> {
/// Set this commit to use the given `headers`, completely replacing the ones it might currently have.
pub fn set_headers(&mut self, header: &Headers) {
header.set_in_commit(self)
}
}

/// Access
Expand Down Expand Up @@ -563,6 +613,15 @@ impl<'repo> Commit<'repo> {
pub fn headers(&self) -> Option<Headers> {
Headers::try_from_commit(&self.inner)
}

/// Return the stored change-id if present, or derive a deterministic fallback from the commit id.
pub fn change_id(&self) -> ChangeId {
self.headers()
.unwrap_or_default()
.ensure_change_id(self.id.detach())
.change_id
.expect("change-id is ensured")
}
}

/// Conflict specific details
Expand Down
Loading
Loading