Skip to content

Commit 806c67d

Browse files
fix: diff merge commits against merge-base with regression test (#)
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
1 parent 17771b0 commit 806c67d

File tree

10 files changed

+87
-22
lines changed

10 files changed

+87
-22
lines changed

crates/but-api/src/diff.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub mod json {
3535
fn from(value: but_core::diff::CommitDetails) -> Self {
3636
let but_core::diff::CommitDetails {
3737
commit,
38-
diff_with_first_parent,
38+
changes: diff_with_first_parent,
3939
line_stats,
4040
conflict_entries,
4141
} = value;

crates/but-core/src/diff/commit_details.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
pub struct CommitDetails {
44
/// The fully decoded commit.
55
pub commit: crate::CommitOwned,
6-
/// The changes between the tree of the first parent and this commit.
7-
pub diff_with_first_parent: Vec<crate::TreeChange>,
6+
/// The changes between the tree of the first parent and this commit, or the merge-base between
7+
/// all parents of this commit and this commit if it's a merge-commit.
8+
pub changes: Vec<crate::TreeChange>,
89
/// The stats of the changes, which are computed only when explicitly requested.
910
pub line_stats: Option<LineStats>,
1011
/// Represents what was causing a particular commit to conflict when rebased.
@@ -30,7 +31,8 @@ impl CommitDetails {
3031
pub fn from_commit_id(commit_id: gix::Id, line_stats: bool) -> anyhow::Result<Self> {
3132
let repo = commit_id.repo;
3233
let commit = repo.find_commit(commit_id)?;
33-
let first_parent_commit_id = commit.parent_ids().map(|id| id.detach()).next();
34+
let first_parent_commit_id =
35+
super::diff_base_commit_id(repo, commit.parent_ids().map(|id| id.detach()))?;
3436

3537
let changes =
3638
crate::diff::TreeChanges::from_trees(repo, first_parent_commit_id, commit_id.detach())?;
@@ -42,7 +44,7 @@ impl CommitDetails {
4244
let conflict_entries = commit.conflict_entries()?;
4345
Ok(CommitDetails {
4446
commit: commit.detach(),
45-
diff_with_first_parent: changes.into_tree_changes(),
47+
changes: changes.into_tree_changes(),
4648
line_stats: line_stats.map(Into::into),
4749
conflict_entries,
4850
})

crates/but-core/src/diff/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,30 @@ impl ModeFlags {
107107
}
108108
}
109109
}
110+
/// Select the commit/tree to diff against:
111+
/// - root commits use `None` (empty tree),
112+
/// - regular commits use their first and only parent,
113+
/// - merge commits use the merge-base of all parents.
114+
pub(crate) fn diff_base_commit_id(
115+
repo: &gix::Repository,
116+
mut parent_ids: impl Iterator<Item = gix::ObjectId>,
117+
) -> anyhow::Result<Option<gix::ObjectId>> {
118+
let Some(first_parent_id) = parent_ids.next() else {
119+
return Ok(None);
120+
};
121+
let Some(second_parent_id) = parent_ids.next() else {
122+
return Ok(Some(first_parent_id));
123+
};
124+
125+
Ok(Some(
126+
repo.merge_base_octopus(
127+
[first_parent_id, second_parent_id]
128+
.into_iter()
129+
.chain(parent_ids),
130+
)?
131+
.detach(),
132+
))
133+
}
110134

111135
#[cfg(test)]
112136
mod tests {

crates/but-core/src/diff/ui.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,8 @@ pub fn commit_changes_with_line_stats_by_worktree_dir(
1717
repo: &gix::Repository,
1818
commit_id: gix::ObjectId,
1919
) -> anyhow::Result<TreeChanges> {
20-
let parent_id = commit_id
21-
.attach(repo)
22-
.object()?
23-
.into_commit()
24-
.parent_ids()
25-
.map(|id| id.detach())
26-
.next();
20+
let commit = commit_id.attach(repo).object()?.into_commit();
21+
let parent_id = super::diff_base_commit_id(repo, commit.parent_ids().map(|id| id.detach()))?;
2722
let (changes, stats) = super::tree_changes_with_line_stats(repo, parent_id, commit_id)
2823
.map(|(c, s)| (c.into_iter().map(Into::into).collect(), s.into()))?;
2924
Ok(TreeChanges { changes, stats })

crates/but-core/tests/core/diff/ui.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,3 +739,48 @@ pub fn repo(name: &str) -> anyhow::Result<gix::Repository> {
739739
gix::open::Options::isolated(),
740740
)?)
741741
}
742+
743+
#[test]
744+
fn merge_commit_ui_diff_uses_merge_base() -> anyhow::Result<()> {
745+
let repo =
746+
but_testsupport::read_only_in_memory_scenario("merge-with-two-branches-line-offset")?;
747+
let merge_commit_id = repo.rev_parse_single("merge")?.detach();
748+
let actual =
749+
but_core::diff::ui::commit_changes_with_line_stats_by_worktree_dir(&repo, merge_commit_id)?;
750+
assert_eq!(actual.stats.lines_added, 19);
751+
assert_eq!(actual.stats.lines_removed, 0);
752+
assert_eq!(actual.stats.files_changed, 1);
753+
754+
let actual_diff = actual.try_to_unidiff(&repo, 0)?;
755+
let actual_diff: &[u8] = actual_diff.as_ref();
756+
assert_eq!(
757+
actual_diff,
758+
br#"--- a/file
759+
+++ b/file
760+
@@ -1,0 +1,9 @@
761+
+1
762+
+2
763+
+3
764+
+4
765+
+5
766+
+6
767+
+7
768+
+8
769+
+9
770+
@@ -12,0 +21,10 @@
771+
+21
772+
+22
773+
+23
774+
+24
775+
+25
776+
+26
777+
+27
778+
+28
779+
+29
780+
+30
781+
782+
"#
783+
);
784+
785+
Ok(())
786+
}

crates/but/src/command/legacy/diff/show.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ pub(crate) fn commit(
106106

107107
if let Some(json_out) = out.for_json() {
108108
let changes: Vec<JsonChange> = result
109-
.diff_with_first_parent
109+
.changes
110110
.into_iter()
111111
.filter(|change| path.as_ref().is_none_or(|p| p == &change.path))
112112
.map(|change| {
@@ -120,7 +120,7 @@ pub(crate) fn commit(
120120
let output = JsonDiffOutput { changes };
121121
json_out.write_value(output)?;
122122
} else if let Some(out) = out.for_human_or_shell() {
123-
for change in result.diff_with_first_parent {
123+
for change in result.changes {
124124
if path.as_ref().is_none_or(|p| p == &change.path) {
125125
let patch = but_api::legacy::diff::tree_change_diffs(ctx, change.clone().into())
126126
.ok()

crates/but/src/command/legacy/reword.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,12 @@ fn edit_commit_message_by_id(
127127
prepare_provided_message(message, "commit message").unwrap_or_else(|| {
128128
let changed_files = get_changed_files_from_commit_details(&commit_details);
129129

130-
let should_show_diff = show_diff_in_editor.should_show_diff(|| {
131-
estimate_diff_blob_size(&commit_details.diff_with_first_parent, ctx)
132-
})?;
130+
let should_show_diff = show_diff_in_editor
131+
.should_show_diff(|| estimate_diff_blob_size(&commit_details.changes, ctx))?;
133132
let diff = should_show_diff
134133
.then(|| {
135134
commit_details
136-
.diff_with_first_parent
135+
.changes
137136
.iter()
138137
.map(|change| change.unified_diff(&*ctx.repo.get()?, 3))
139138
.filter_map(|diff| diff.transpose())
@@ -174,7 +173,7 @@ fn get_changed_files_from_commit_details(
174173
) -> Vec<String> {
175174
let mut files = Vec::new();
176175

177-
for change in &commit_details.diff_with_first_parent {
176+
for change in &commit_details.changes {
178177
let status = match &change.status {
179178
but_core::TreeStatus::Addition { .. } => "new file:",
180179
but_core::TreeStatus::Deletion { .. } => "deleted:",

crates/but/src/command/legacy/status/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,7 @@ pub fn print_group(
789789
&repo,
790790
commit.short_id.clone(),
791791
&commit.inner,
792-
CommitChanges::Remote(&details.diff_with_first_parent),
792+
CommitChanges::Remote(&details.changes),
793793
CommitClassification::Upstream,
794794
false,
795795
show_files,

crates/but/src/tui/diff_viewer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ impl DiffFileEntry {
116116
but_api::diff::commit_details(ctx, commit_id, but_api::diff::ComputeLineStats::No)?;
117117

118118
result
119-
.diff_with_first_parent
119+
.changes
120120
.into_iter()
121121
.filter(|change| path_filter.as_ref().is_none_or(|p| p == &change.path))
122122
.map(|change| {

crates/gitbutler-branch-actions/tests/virtual_branches/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub fn list_commit_files(
8989
gix::prelude::ObjectIdExt::attach(commit_id, &repo),
9090
false,
9191
)
92-
.map(|d| d.diff_with_first_parent)
92+
.map(|d| d.changes)
9393
}
9494

9595
pub fn create_commit(

0 commit comments

Comments
 (0)