Skip to content

repo: merge commit changes into tree#13765

Draft
estib-vega wants to merge 3 commits into
masterfrom
merge-changes-into-tree
Draft

repo: merge commit changes into tree#13765
estib-vega wants to merge 3 commits into
masterfrom
merge-changes-into-tree

Conversation

@estib-vega
Copy link
Copy Markdown
Contributor

Add a but-core repository helper for combining the changes introduced by selected commits into a single tree, along with support for writing GitButler conflicted trees.

This introduces the low-level building blocks needed for squash-style operations that should merge only the selected commit changes, without accidentally pulling in unrelated parent state.

How is this done?

The first relevant function is plan_commit_changes_for_merge.
This function iterates over the selected commits, and figures out which base to use for the merge ops.
At this step, we simplify the merges down to only the necessary ones.

The second (and main function) is merge_commit_changes_to_tree.
This function calls plan_commit_changes_for_merge, and executes the merge ops in order.
Starting with the common merge base of all commits to be merged.
If there is a conflict encountered, we stop and return where we stopped

Why

This enables us to bypass having to sort the subject commits of a squash around the target, and just grabbing the actual changes to produce the tree of the squash commit.

@github-actions github-actions Bot added the rust Pull requests that update Rust code label May 12, 2026
@estib-vega estib-vega requested a review from Copilot May 12, 2026 10:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new low-level repository helpers in but-core to (1) plan and merge selected commits’ change ranges into a single tree (avoiding unselected-parent state leakage), and (2) persist GitButler-style conflicted trees with side trees + conflict metadata. This is intended as foundational support for squash-style operations that combine only chosen commit changes.

Changes:

  • Introduces RepositoryExt::merge_commit_changes_to_tree() plus planning/output types (PlannedCommitChange, MergeCommitChangesOutcome, MergeCommitChangesConflict).
  • Adds commit::write_conflicted_tree() for writing GitButler conflicted wrapper trees.
  • Adds multiple scripted fixture scenarios and integration tests covering octopus merges, non-contiguous selections, unselected-parent exclusion, and fail-fast-on-conflict behavior.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/but-core/src/repo_ext.rs Implements merge planning + merge execution into a single tree; adds conflict metadata extraction utilities.
crates/but-core/src/lib.rs Re-exports new merge/planning types and helpers from repo_ext.
crates/but-core/src/commit/mod.rs Adds write_conflicted_tree() to persist GitButler conflicted trees.
crates/but-core/tests/core/repo_ext.rs Adds integration tests validating merge planning and resulting trees across multiple graphs.
crates/but-core/tests/core/main.rs Registers the new repo_ext integration test module.
crates/but-core/tests/fixtures/scenario/three-branches-three-commits.sh New fixture repo with three branches and three commits each for planner behavior tests.
crates/but-core/tests/fixtures/scenario/octopus-merge-with-redundant-input.sh New fixture to compare “merge selected changes” vs a clean octopus merge result.
crates/but-core/tests/fixtures/scenario/merge-commits-preserve-noncontiguous-selected-changes.sh New fixture to validate skipping unselected middle commits while keeping later selected changes.
crates/but-core/tests/fixtures/scenario/merge-commits-excludes-unselected-parent.sh New fixture to validate excluding unselected parent changes when merging selected descendants.
crates/but-core/tests/fixtures/scenario/merge-commit-changes-fail-fast-after-conflict.sh New fixture to validate fail-fast folding when an earlier merge step conflicts.

pub use repo_ext::{RepositoryExt, update_head_reference};
pub use repo_ext::{
MergeCommitChangesConflict, MergeCommitChangesOutcome, PlannedCommitChange, RepositoryExt,
plan_commit_changes_for_merge_for_tests, update_head_reference,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybeeee

Comment thread crates/but-core/src/repo_ext.rs Outdated
Comment thread crates/but-core/tests/core/repo_ext.rs Outdated
Add a function to the repository extension to merge the changes in from
different commits into a tree that can be used for e.g. squashing
commits.
Add code to write the GitButler conflicted tree out.
Deduplicate the code related to the extraction of GitButler conflict
metadata out of gix merge outcomes.
Share it across rebase, and gitbutler-repo.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment on lines +120 to +121
plan_commit_changes_for_merge_for_tests, update_head_reference,
};
Copy link
Copy Markdown
Collaborator

@Byron Byron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting!

As it's implemented on an immutable &gix::Repository, it's naturally indifferent to whether the merge is in-memory or not, letting the caller decide. Thus it works nicely in dry-run mode.

This PR will contain CommitId based merge-base helpers that you could already use here.

}

let merge_base = self
.merge_base_octopus(merge_plan.iter().map(|change| change.commit_id))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I wouldn't push for this just yet, I think once but-graph is more obviously commit oriented and Segemnts went away (which really are just logical structuring without functional impact), then it would be natural to the graph to find the merge-base.

The octopus-merge already exists here.

self.inner
.neighbors_directed(ws_tip_segment.id, Direction::Outgoing)
.reduce(|a, b| self.find_git_merge_base(a, b).unwrap_or(a))
.and_then(|base| self[base].commits.first().map(|c| (c.id, base)))

If that was a method on but-graph, it could be used today, and creating one is no problem either.

The inconvenience is just the translation from CommitId to SegmentId, for which helpers could also exist or maybe a mapping even. The benefit of this is just performance in the form of "not doing work twice".

So this is just a note, a heads-up, and a note to self that getting rid of all gix::Repository based merge-base computations is a worthwhile pursuit.

CC @Caleb-T-Owens this is what would motivate me to restructure but-graph to support this, segments or not. Just needs a mapping. And nowadays, while talking about it, it can actually be done :D.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants