-
-
Notifications
You must be signed in to change notification settings - Fork 318
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1611 from Byron/merge
octopus-merge (part 3.5: gix-api and CLI)
- Loading branch information
Showing
24 changed files
with
552 additions
and
31 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
use crate::OutputFormat; | ||
use anyhow::{bail, Context}; | ||
use gix::bstr::BString; | ||
use gix::bstr::ByteSlice; | ||
use gix::merge::blob::builtin_driver::binary; | ||
use gix::merge::blob::builtin_driver::text::Conflict; | ||
use gix::merge::blob::pipeline::WorktreeRoots; | ||
use gix::merge::blob::{Resolution, ResourceKind}; | ||
use gix::object::tree::EntryKind; | ||
use gix::Id; | ||
use std::path::Path; | ||
|
||
pub fn file( | ||
repo: gix::Repository, | ||
out: &mut dyn std::io::Write, | ||
format: OutputFormat, | ||
conflict: Option<gix::merge::blob::builtin_driver::text::Conflict>, | ||
base: BString, | ||
ours: BString, | ||
theirs: BString, | ||
) -> anyhow::Result<()> { | ||
if format != OutputFormat::Human { | ||
bail!("JSON output isn't implemented yet"); | ||
} | ||
let index = &repo.index_or_load_from_head()?; | ||
let specs = repo.pathspec( | ||
false, | ||
[base, ours, theirs], | ||
true, | ||
index, | ||
gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping.adjust_for_bare(repo.is_bare()), | ||
)?; | ||
// TODO: there should be a way to normalize paths without going through patterns, at least in this case maybe? | ||
// `Search` actually sorts patterns by excluding or not, all that can lead to strange results. | ||
let mut patterns = specs.search().patterns().map(|p| p.path().to_owned()); | ||
let base = patterns.next().unwrap(); | ||
let ours = patterns.next().unwrap(); | ||
let theirs = patterns.next().unwrap(); | ||
|
||
let base_id = repo.rev_parse_single(base.as_bstr()).ok(); | ||
let ours_id = repo.rev_parse_single(ours.as_bstr()).ok(); | ||
let theirs_id = repo.rev_parse_single(theirs.as_bstr()).ok(); | ||
let roots = worktree_roots(base_id, ours_id, theirs_id, repo.work_dir())?; | ||
|
||
let mut cache = repo.merge_resource_cache(roots)?; | ||
let null = repo.object_hash().null(); | ||
cache.set_resource( | ||
base_id.map_or(null, Id::detach), | ||
EntryKind::Blob, | ||
base.as_bstr(), | ||
ResourceKind::CommonAncestorOrBase, | ||
&repo.objects, | ||
)?; | ||
cache.set_resource( | ||
ours_id.map_or(null, Id::detach), | ||
EntryKind::Blob, | ||
ours.as_bstr(), | ||
ResourceKind::CurrentOrOurs, | ||
&repo.objects, | ||
)?; | ||
cache.set_resource( | ||
theirs_id.map_or(null, Id::detach), | ||
EntryKind::Blob, | ||
theirs.as_bstr(), | ||
ResourceKind::OtherOrTheirs, | ||
&repo.objects, | ||
)?; | ||
|
||
let mut options = repo.blob_merge_options()?; | ||
if let Some(conflict) = conflict { | ||
options.text.conflict = conflict; | ||
options.resolve_binary_with = match conflict { | ||
Conflict::Keep { .. } => None, | ||
Conflict::ResolveWithOurs => Some(binary::ResolveWith::Ours), | ||
Conflict::ResolveWithTheirs => Some(binary::ResolveWith::Theirs), | ||
Conflict::ResolveWithUnion => None, | ||
}; | ||
} | ||
let platform = cache.prepare_merge(&repo.objects, options)?; | ||
let labels = gix::merge::blob::builtin_driver::text::Labels { | ||
ancestor: Some(base.as_bstr()), | ||
current: Some(ours.as_bstr()), | ||
other: Some(theirs.as_bstr()), | ||
}; | ||
let mut buf = repo.empty_reusable_buffer(); | ||
let (pick, resolution) = platform.merge(&mut buf, labels, repo.command_context()?)?; | ||
let buf = platform.buffer_by_pick(pick).unwrap_or(&buf); | ||
out.write_all(buf)?; | ||
|
||
if resolution == Resolution::Conflict { | ||
bail!("File conflicted") | ||
} | ||
Ok(()) | ||
} | ||
|
||
fn worktree_roots( | ||
base: Option<gix::Id<'_>>, | ||
ours: Option<gix::Id<'_>>, | ||
theirs: Option<gix::Id<'_>>, | ||
workdir: Option<&Path>, | ||
) -> anyhow::Result<gix::merge::blob::pipeline::WorktreeRoots> { | ||
let roots = if base.is_none() || ours.is_none() || theirs.is_none() { | ||
let workdir = workdir.context("A workdir is required if one of the bases are provided as path.")?; | ||
gix::merge::blob::pipeline::WorktreeRoots { | ||
current_root: ours.is_none().then(|| workdir.to_owned()), | ||
other_root: theirs.is_none().then(|| workdir.to_owned()), | ||
common_ancestor_root: base.is_none().then(|| workdir.to_owned()), | ||
} | ||
} else { | ||
WorktreeRoots::default() | ||
}; | ||
Ok(roots) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
use crate::config; | ||
use crate::config::tree::SubSectionRequirement; | ||
use crate::config::{ | ||
tree::{keys, Key, Merge, Section}, | ||
Tree, | ||
}; | ||
|
||
impl Merge { | ||
/// The `merge.renormalize` key | ||
pub const RENORMALIZE: keys::Boolean = keys::Boolean::new_boolean("renormalize", &Tree::MERGE); | ||
/// The `merge.default` key | ||
pub const DEFAULT: keys::String = keys::String::new_string("default", &Tree::MERGE); | ||
/// The `merge.<driver>.name` key. | ||
pub const DRIVER_NAME: keys::String = keys::String::new_string("name", &config::Tree::MERGE) | ||
.with_subsection_requirement(Some(SubSectionRequirement::Parameter("driver"))); | ||
/// The `merge.<driver>.driver` key. | ||
pub const DRIVER_COMMAND: keys::Program = keys::Program::new_program("driver", &config::Tree::MERGE) | ||
.with_subsection_requirement(Some(SubSectionRequirement::Parameter("driver"))); | ||
/// The `merge.<driver>.recursive` key. | ||
pub const DRIVER_RECURSIVE: keys::String = keys::String::new_string("recursive", &config::Tree::MERGE) | ||
.with_subsection_requirement(Some(SubSectionRequirement::Parameter("driver"))); | ||
/// The `merge.conflictStyle` key. | ||
#[cfg(feature = "blob-merge")] | ||
pub const CONFLICT_STYLE: ConflictStyle = | ||
ConflictStyle::new_with_validate("conflictStyle", &config::Tree::MERGE, validate::ConflictStyle); | ||
} | ||
|
||
impl Section for Merge { | ||
fn name(&self) -> &str { | ||
"merge" | ||
} | ||
|
||
fn keys(&self) -> &[&dyn Key] { | ||
&[ | ||
&Self::RENORMALIZE, | ||
&Self::DEFAULT, | ||
&Self::DRIVER_NAME, | ||
&Self::DRIVER_COMMAND, | ||
&Self::DRIVER_RECURSIVE, | ||
] | ||
} | ||
} | ||
|
||
/// The `merge.conflictStyle` key. | ||
#[cfg(feature = "blob-merge")] | ||
pub type ConflictStyle = keys::Any<validate::ConflictStyle>; | ||
|
||
#[cfg(feature = "blob-merge")] | ||
mod conflict_style { | ||
use crate::{bstr::BStr, config, config::tree::sections::merge::ConflictStyle}; | ||
use gix_merge::blob::builtin_driver::text; | ||
use std::borrow::Cow; | ||
|
||
impl ConflictStyle { | ||
/// Derive the diff algorithm identified by `name`, case-insensitively. | ||
pub fn try_into_conflict_style( | ||
&'static self, | ||
name: Cow<'_, BStr>, | ||
) -> Result<text::ConflictStyle, config::key::GenericErrorWithValue> { | ||
let style = if name.as_ref() == "merge" { | ||
text::ConflictStyle::Merge | ||
} else if name.as_ref() == "diff3" { | ||
text::ConflictStyle::Diff3 | ||
} else if name.as_ref() == "zdiff3" { | ||
text::ConflictStyle::ZealousDiff3 | ||
} else { | ||
return Err(config::key::GenericErrorWithValue::from_value(self, name.into_owned())); | ||
}; | ||
Ok(style) | ||
} | ||
} | ||
} | ||
|
||
#[cfg(feature = "blob-merge")] | ||
mod validate { | ||
use crate::{ | ||
bstr::BStr, | ||
config::tree::{keys, Merge}, | ||
}; | ||
|
||
pub struct ConflictStyle; | ||
impl keys::Validate for ConflictStyle { | ||
fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { | ||
Merge::CONFLICT_STYLE.try_into_conflict_style(value.into())?; | ||
Ok(()) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.