Skip to content

Commit dee9fdc

Browse files
committed
feat!: add commit::merge_base() to produce the single merge-base to use.
This allows more flexibility in conjunction with tree-merging, as commits as input aren't required. This is breaking as it changes the return value of `commit()`.
1 parent c5955fc commit dee9fdc

File tree

4 files changed

+311
-227
lines changed

4 files changed

+311
-227
lines changed

gix-merge/src/commit.rs

-227
This file was deleted.

gix-merge/src/commit/function.rs

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use crate::blob::builtin_driver;
2+
use crate::commit::{Error, Options};
3+
use gix_object::FindExt;
4+
use std::borrow::Cow;
5+
6+
/// Like [`tree()`](crate::tree()), but it takes only two commits, `our_commit` and `their_commit` to automatically
7+
/// compute the merge-bases among them.
8+
/// If there are multiple merge bases, these will be auto-merged into one, recursively, if
9+
/// [`allow_missing_merge_base`](Options::allow_missing_merge_base) is `true`.
10+
///
11+
/// `labels` are names where [`current`](crate::blob::builtin_driver::text::Labels::current) is a name for `our_commit`
12+
/// and [`other`](crate::blob::builtin_driver::text::Labels::other) is a name for `their_commit`.
13+
/// If [`ancestor`](crate::blob::builtin_driver::text::Labels::ancestor) is unset, it will be set by us based on the
14+
/// merge-bases of `our_commit` and `their_commit`.
15+
///
16+
/// The `graph` is used to find the merge-base between `our_commit` and `their_commit`, and can also act as cache
17+
/// to speed up subsequent merge-base queries.
18+
///
19+
/// Use `abbreviate_hash(id)` to shorten the given `id` according to standard git shortening rules. It's used in case
20+
/// the ancestor-label isn't explicitly set so that the merge base label becomes the shortened `id`.
21+
/// Note that it's a dyn closure only to make it possible to recursively call this function in case of multiple merge-bases.
22+
///
23+
/// `write_object` is used only if it's allowed to merge multiple merge-bases into one, and if there
24+
/// are multiple merge bases, and to write merged buffers as blobs.
25+
///
26+
/// ### Performance
27+
///
28+
/// Note that `objects` *should* have an object cache to greatly accelerate tree-retrieval.
29+
///
30+
/// ### Notes
31+
///
32+
/// When merging merge-bases recursively, the options are adjusted automatically to act like Git, i.e. merge binary
33+
/// blobs and resolve with *ours*, while resorting to using the base/ancestor in case of unresolvable conflicts.
34+
///
35+
/// ### Deviation
36+
///
37+
/// * It's known that certain conflicts around symbolic links can be auto-resolved. We don't have an option for this
38+
/// at all, yet, primarily as Git seems to not implement the *ours*/*theirs* choice in other places even though it
39+
/// reasonably could. So we leave it to the caller to continue processing the returned tree at will.
40+
#[allow(clippy::too_many_arguments)]
41+
pub fn commit<'objects>(
42+
our_commit: gix_hash::ObjectId,
43+
their_commit: gix_hash::ObjectId,
44+
labels: builtin_driver::text::Labels<'_>,
45+
graph: &mut gix_revwalk::Graph<'_, '_, gix_revwalk::graph::Commit<gix_revision::merge_base::Flags>>,
46+
diff_resource_cache: &mut gix_diff::blob::Platform,
47+
blob_merge: &mut crate::blob::Platform,
48+
objects: &'objects (impl gix_object::FindObjectOrHeader + gix_object::Write),
49+
abbreviate_hash: &mut dyn FnMut(&gix_hash::oid) -> String,
50+
options: Options,
51+
) -> Result<super::Outcome<'objects>, Error> {
52+
let merge_bases = gix_revision::merge_base(our_commit, &[their_commit], graph)?;
53+
let mut virtual_merge_bases = Vec::new();
54+
let mut state = gix_diff::tree::State::default();
55+
let mut commit_to_tree =
56+
|commit_id: gix_hash::ObjectId| objects.find_commit(&commit_id, &mut state.buf1).map(|c| c.tree());
57+
58+
let (merge_base_tree_id, ancestor_name): (_, Cow<'_, str>) = match merge_bases.clone() {
59+
Some(base_commit) if base_commit.len() == 1 => {
60+
(commit_to_tree(base_commit[0])?, abbreviate_hash(&base_commit[0]).into())
61+
}
62+
Some(base_commits) => {
63+
let virtual_base_tree = if options.use_first_merge_base {
64+
let first = *base_commits.first().expect("if Some() there is at least one.");
65+
commit_to_tree(first)?
66+
} else {
67+
let out = crate::commit::merge_base(
68+
base_commits,
69+
graph,
70+
diff_resource_cache,
71+
blob_merge,
72+
objects,
73+
abbreviate_hash,
74+
options.tree_merge.clone(),
75+
)?;
76+
virtual_merge_bases = out.virtual_merge_bases;
77+
out.tree_id
78+
};
79+
(virtual_base_tree, "merged common ancestors".into())
80+
}
81+
None => {
82+
if options.allow_missing_merge_base {
83+
(gix_hash::ObjectId::empty_tree(our_commit.kind()), "empty tree".into())
84+
} else {
85+
return Err(Error::NoMergeBase {
86+
our_commit_id: our_commit,
87+
their_commit_id: their_commit,
88+
});
89+
}
90+
}
91+
};
92+
93+
let mut labels = labels; // TODO(borrowchk): this re-assignment shouldn't be needed.
94+
if labels.ancestor.is_none() {
95+
labels.ancestor = Some(ancestor_name.as_ref().into());
96+
}
97+
98+
let our_tree_id = objects.find_commit(&our_commit, &mut state.buf1)?.tree();
99+
let their_tree_id = objects.find_commit(&their_commit, &mut state.buf1)?.tree();
100+
101+
let outcome = crate::tree(
102+
&merge_base_tree_id,
103+
&our_tree_id,
104+
&their_tree_id,
105+
labels,
106+
objects,
107+
|buf| objects.write_buf(gix_object::Kind::Blob, buf),
108+
&mut state,
109+
diff_resource_cache,
110+
blob_merge,
111+
options.tree_merge,
112+
)?;
113+
114+
Ok(super::Outcome {
115+
tree_merge: outcome,
116+
merge_bases,
117+
merge_base_tree_id,
118+
virtual_merge_bases,
119+
})
120+
}

0 commit comments

Comments
 (0)