|  | 
|  | 1 | +use crate::OutputFormat; | 
|  | 2 | +use anyhow::{bail, Context}; | 
|  | 3 | +use gix::bstr::BString; | 
|  | 4 | +use gix::bstr::ByteSlice; | 
|  | 5 | +use gix::merge::blob::builtin_driver::binary; | 
|  | 6 | +use gix::merge::blob::builtin_driver::text::Conflict; | 
|  | 7 | +use gix::merge::blob::pipeline::WorktreeRoots; | 
|  | 8 | +use gix::merge::blob::{Resolution, ResourceKind}; | 
|  | 9 | +use gix::object::tree::EntryKind; | 
|  | 10 | +use gix::Id; | 
|  | 11 | +use std::path::Path; | 
|  | 12 | + | 
|  | 13 | +pub fn file( | 
|  | 14 | +    repo: gix::Repository, | 
|  | 15 | +    out: &mut dyn std::io::Write, | 
|  | 16 | +    format: OutputFormat, | 
|  | 17 | +    conflict: Option<gix::merge::blob::builtin_driver::text::Conflict>, | 
|  | 18 | +    base: BString, | 
|  | 19 | +    ours: BString, | 
|  | 20 | +    theirs: BString, | 
|  | 21 | +) -> anyhow::Result<()> { | 
|  | 22 | +    if format != OutputFormat::Human { | 
|  | 23 | +        bail!("JSON output isn't implemented yet"); | 
|  | 24 | +    } | 
|  | 25 | +    let index = &repo.index_or_load_from_head()?; | 
|  | 26 | +    let specs = repo.pathspec( | 
|  | 27 | +        false, | 
|  | 28 | +        [base, ours, theirs], | 
|  | 29 | +        true, | 
|  | 30 | +        index, | 
|  | 31 | +        gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping.adjust_for_bare(repo.is_bare()), | 
|  | 32 | +    )?; | 
|  | 33 | +    // TODO: there should be a way to normalize paths without going through patterns, at least in this case maybe? | 
|  | 34 | +    //       `Search` actually sorts patterns by excluding or not, all that can lead to strange results. | 
|  | 35 | +    let mut patterns = specs.search().patterns().map(|p| p.path().to_owned()); | 
|  | 36 | +    let base = patterns.next().unwrap(); | 
|  | 37 | +    let ours = patterns.next().unwrap(); | 
|  | 38 | +    let theirs = patterns.next().unwrap(); | 
|  | 39 | + | 
|  | 40 | +    let base_id = repo.rev_parse_single(base.as_bstr()).ok(); | 
|  | 41 | +    let ours_id = repo.rev_parse_single(ours.as_bstr()).ok(); | 
|  | 42 | +    let theirs_id = repo.rev_parse_single(theirs.as_bstr()).ok(); | 
|  | 43 | +    let roots = worktree_roots(base_id, ours_id, theirs_id, repo.work_dir())?; | 
|  | 44 | + | 
|  | 45 | +    let mut cache = repo.merge_resource_cache(roots)?; | 
|  | 46 | +    let null = repo.object_hash().null(); | 
|  | 47 | +    cache.set_resource( | 
|  | 48 | +        base_id.map_or(null, Id::detach), | 
|  | 49 | +        EntryKind::Blob, | 
|  | 50 | +        base.as_bstr(), | 
|  | 51 | +        ResourceKind::CommonAncestorOrBase, | 
|  | 52 | +        &repo.objects, | 
|  | 53 | +    )?; | 
|  | 54 | +    cache.set_resource( | 
|  | 55 | +        ours_id.map_or(null, Id::detach), | 
|  | 56 | +        EntryKind::Blob, | 
|  | 57 | +        ours.as_bstr(), | 
|  | 58 | +        ResourceKind::CurrentOrOurs, | 
|  | 59 | +        &repo.objects, | 
|  | 60 | +    )?; | 
|  | 61 | +    cache.set_resource( | 
|  | 62 | +        theirs_id.map_or(null, Id::detach), | 
|  | 63 | +        EntryKind::Blob, | 
|  | 64 | +        theirs.as_bstr(), | 
|  | 65 | +        ResourceKind::OtherOrTheirs, | 
|  | 66 | +        &repo.objects, | 
|  | 67 | +    )?; | 
|  | 68 | + | 
|  | 69 | +    let mut options = repo.blob_merge_options()?; | 
|  | 70 | +    if let Some(conflict) = conflict { | 
|  | 71 | +        options.text.conflict = conflict; | 
|  | 72 | +        options.resolve_binary_with = match conflict { | 
|  | 73 | +            Conflict::Keep { .. } => None, | 
|  | 74 | +            Conflict::ResolveWithOurs => Some(binary::ResolveWith::Ours), | 
|  | 75 | +            Conflict::ResolveWithTheirs => Some(binary::ResolveWith::Theirs), | 
|  | 76 | +            Conflict::ResolveWithUnion => None, | 
|  | 77 | +        }; | 
|  | 78 | +    } | 
|  | 79 | +    let platform = cache.prepare_merge(&repo.objects, options)?; | 
|  | 80 | +    let labels = gix::merge::blob::builtin_driver::text::Labels { | 
|  | 81 | +        ancestor: Some(base.as_bstr()), | 
|  | 82 | +        current: Some(ours.as_bstr()), | 
|  | 83 | +        other: Some(theirs.as_bstr()), | 
|  | 84 | +    }; | 
|  | 85 | +    let mut buf = repo.empty_reusable_buffer(); | 
|  | 86 | +    let (pick, resolution) = platform.merge(&mut buf, labels, repo.command_context()?)?; | 
|  | 87 | +    let buf = platform.buffer_by_pick(pick).unwrap_or(&buf); | 
|  | 88 | +    out.write_all(buf)?; | 
|  | 89 | + | 
|  | 90 | +    if resolution == Resolution::Conflict { | 
|  | 91 | +        bail!("File conflicted") | 
|  | 92 | +    } | 
|  | 93 | +    Ok(()) | 
|  | 94 | +} | 
|  | 95 | + | 
|  | 96 | +fn worktree_roots( | 
|  | 97 | +    base: Option<gix::Id<'_>>, | 
|  | 98 | +    ours: Option<gix::Id<'_>>, | 
|  | 99 | +    theirs: Option<gix::Id<'_>>, | 
|  | 100 | +    workdir: Option<&Path>, | 
|  | 101 | +) -> anyhow::Result<gix::merge::blob::pipeline::WorktreeRoots> { | 
|  | 102 | +    let roots = if base.is_none() || ours.is_none() || theirs.is_none() { | 
|  | 103 | +        let workdir = workdir.context("A workdir is required if one of the bases are provided as path.")?; | 
|  | 104 | +        gix::merge::blob::pipeline::WorktreeRoots { | 
|  | 105 | +            current_root: ours.is_none().then(|| workdir.to_owned()), | 
|  | 106 | +            other_root: theirs.is_none().then(|| workdir.to_owned()), | 
|  | 107 | +            common_ancestor_root: base.is_none().then(|| workdir.to_owned()), | 
|  | 108 | +        } | 
|  | 109 | +    } else { | 
|  | 110 | +        WorktreeRoots::default() | 
|  | 111 | +    }; | 
|  | 112 | +    Ok(roots) | 
|  | 113 | +} | 
0 commit comments