Skip to content

Commit 105ea9d

Browse files
committed
feat: add gix merge commits
1 parent 17def54 commit 105ea9d

File tree

4 files changed

+136
-0
lines changed

4 files changed

+136
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use crate::OutputFormat;
2+
use anyhow::{anyhow, bail, Context};
3+
use gix::bstr::BString;
4+
use gix::bstr::ByteSlice;
5+
use gix::merge::tree::UnresolvedConflict;
6+
use gix::prelude::Write;
7+
8+
use super::tree::Options;
9+
10+
#[allow(clippy::too_many_arguments)]
11+
pub fn commit(
12+
mut repo: gix::Repository,
13+
out: &mut dyn std::io::Write,
14+
err: &mut dyn std::io::Write,
15+
ours: BString,
16+
theirs: BString,
17+
Options {
18+
format,
19+
file_favor,
20+
in_memory,
21+
}: Options,
22+
) -> anyhow::Result<()> {
23+
if format != OutputFormat::Human {
24+
bail!("JSON output isn't implemented yet");
25+
}
26+
repo.object_cache_size_if_unset(repo.compute_object_cache_size_for_tree_diffs(&**repo.index_or_empty()?));
27+
if in_memory {
28+
repo.objects.enable_object_memory();
29+
}
30+
let (ours_ref, ours_id) = refname_and_commit(&repo, ours)?;
31+
let (theirs_ref, theirs_id) = refname_and_commit(&repo, theirs)?;
32+
33+
let options = repo.tree_merge_options()?.with_file_favor(file_favor);
34+
let ours_id_str = ours_id.to_string();
35+
let theirs_id_str = theirs_id.to_string();
36+
let labels = gix::merge::blob::builtin_driver::text::Labels {
37+
ancestor: None,
38+
current: ours_ref
39+
.as_ref()
40+
.map_or(ours_id_str.as_str().into(), |n| n.as_bstr())
41+
.into(),
42+
other: theirs_ref
43+
.as_ref()
44+
.map_or(theirs_id_str.as_str().into(), |n| n.as_bstr())
45+
.into(),
46+
};
47+
let res = repo
48+
.merge_commits(ours_id, theirs_id, labels, options.into())?
49+
.tree_merge;
50+
let has_conflicts = res.conflicts.is_empty();
51+
let has_unresolved_conflicts = res.has_unresolved_conflicts(UnresolvedConflict::Renames);
52+
{
53+
let _span = gix::trace::detail!("Writing merged tree");
54+
let mut written = 0;
55+
let tree_id = res
56+
.tree
57+
.detach()
58+
.write(|tree| {
59+
written += 1;
60+
repo.write(tree)
61+
})
62+
.map_err(|err| anyhow!("{err}"))?;
63+
writeln!(out, "{tree_id} (wrote {written} trees)")?;
64+
}
65+
66+
if !has_conflicts {
67+
writeln!(err, "{} possibly resolved conflicts", res.conflicts.len())?;
68+
}
69+
if has_unresolved_conflicts {
70+
bail!("Tree conflicted")
71+
}
72+
Ok(())
73+
}
74+
75+
fn refname_and_commit(
76+
repo: &gix::Repository,
77+
revspec: BString,
78+
) -> anyhow::Result<(Option<BString>, gix::hash::ObjectId)> {
79+
let spec = repo.rev_parse(revspec.as_bstr())?;
80+
let commit_id = spec
81+
.single()
82+
.context("Expected revspec to expand to a single rev only")?
83+
.object()?
84+
.peel_to_commit()?
85+
.id;
86+
let refname = spec.first_reference().map(|r| r.name.shorten().as_bstr().to_owned());
87+
Ok((refname, commit_id))
88+
}

gitoxide-core/src/repository/merge/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ pub use file::file;
33

44
pub mod tree;
55
pub use tree::function::tree;
6+
7+
mod commit;
8+
pub use commit::commit;

src/plumbing/main.rs

+27
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,33 @@ pub fn main() -> Result<()> {
200200
)
201201
},
202202
),
203+
merge::SubCommands::Commit {
204+
in_memory,
205+
file_favor,
206+
ours,
207+
theirs,
208+
} => prepare_and_run(
209+
"merge-commit",
210+
trace,
211+
verbose,
212+
progress,
213+
progress_keep_open,
214+
None,
215+
move |_progress, out, err| {
216+
core::repository::merge::commit(
217+
repository(Mode::Lenient)?,
218+
out,
219+
err,
220+
ours,
221+
theirs,
222+
core::repository::merge::tree::Options {
223+
format,
224+
file_favor: file_favor.map(Into::into),
225+
in_memory,
226+
},
227+
)
228+
},
229+
),
203230
},
204231
Subcommands::MergeBase(crate::plumbing::options::merge_base::Command { first, others }) => prepare_and_run(
205232
"merge-base",

src/plumbing/options/mod.rs

+18
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,25 @@ pub mod merge {
428428
#[clap(value_name = "BASE", value_parser = crate::shared::AsBString)]
429429
base: BString,
430430
/// A revspec to their treeish.
431+
#[clap(value_name = "THEIRS", value_parser = crate::shared::AsBString)]
432+
theirs: BString,
433+
},
434+
/// Merge a commits by specifying ours, and theirs, writing the tree to the object database.
435+
Commit {
436+
/// Keep all objects to be written in memory to avoid any disk IO.
437+
///
438+
/// Note that the resulting tree won't be available or inspectable.
439+
#[clap(long, short = 'm')]
440+
in_memory: bool,
441+
/// Decide how to resolve content conflicts in files. If unset, write conflict markers and fail.
442+
#[clap(long, short = 'f')]
443+
file_favor: Option<FileFavor>,
444+
445+
/// A revspec to our committish.
431446
#[clap(value_name = "OURS", value_parser = crate::shared::AsBString)]
447+
ours: BString,
448+
/// A revspec to their committish.
449+
#[clap(value_name = "THEIRS", value_parser = crate::shared::AsBString)]
432450
theirs: BString,
433451
},
434452
}

0 commit comments

Comments
 (0)