ok/jj
1
0
Fork 0
forked from mirrors/jj

conflicts: replace ContentHunk with BString

ContentHunk is basically a nice wrapper around Vec<u8>. I think it would give
little benefit for type safety.
This commit is contained in:
Yuya Nishihara 2024-08-26 01:16:01 +09:00
parent dd8ec3dece
commit 1ba581b37c
5 changed files with 57 additions and 81 deletions

View file

@ -2,6 +2,7 @@ use std::borrow::Cow;
use std::path::Path;
use std::sync::Arc;
use bstr::BString;
use futures::StreamExt;
use futures::TryFutureExt;
use futures::TryStreamExt;
@ -16,7 +17,6 @@ use jj_lib::conflicts::MaterializedTreeValue;
use jj_lib::diff::Diff;
use jj_lib::diff::DiffHunk;
use jj_lib::files;
use jj_lib::files::ContentHunk;
use jj_lib::files::MergeResult;
use jj_lib::matchers::Matcher;
use jj_lib::merge::Merge;
@ -534,8 +534,8 @@ fn make_merge_sections(
) -> Result<Vec<scm_record::Section<'static>>, BuiltinToolError> {
let mut sections = Vec::new();
match merge_result {
MergeResult::Resolved(ContentHunk(buf)) => {
let contents = buf_to_file_contents(None, buf);
MergeResult::Resolved(buf) => {
let contents = buf_to_file_contents(None, buf.into());
let section = match contents {
FileContents::Absent => None,
FileContents::Text {
@ -561,7 +561,7 @@ fn make_merge_sections(
MergeResult::Conflict(hunks) => {
for hunk in hunks {
let section = match hunk.into_resolved() {
Ok(ContentHunk(contents)) => {
Ok(contents) => {
let contents = std::str::from_utf8(&contents).map_err(|err| {
BuiltinToolError::DecodeUtf8 {
source: err,
@ -587,7 +587,6 @@ fn make_merge_sections(
.cycle(),
)
.map(|(contents, change_type)| -> Result<_, BuiltinToolError> {
let ContentHunk(contents) = contents;
let contents = std::str::from_utf8(contents).map_err(|err| {
BuiltinToolError::DecodeUtf8 {
source: err,
@ -613,7 +612,7 @@ fn make_merge_sections(
pub fn edit_merge_builtin(
tree: &MergedTree,
path: &RepoPath,
content: Merge<ContentHunk>,
content: Merge<BString>,
) -> Result<MergedTreeId, BuiltinToolError> {
let merge_result = files::merge(&content);
let sections = make_merge_sections(merge_result)?;

View file

@ -6,6 +6,7 @@ use std::process::ExitStatus;
use std::process::Stdio;
use std::sync::Arc;
use bstr::BString;
use itertools::Itertools;
use jj_lib::backend::FileId;
use jj_lib::backend::MergedTreeId;
@ -151,7 +152,7 @@ pub enum ExternalToolError {
pub fn run_mergetool_external(
editor: &ExternalMergeTool,
file_merge: Merge<Option<FileId>>,
content: Merge<jj_lib::files::ContentHunk>,
content: Merge<BString>,
repo_path: &RepoPath,
conflict: MergedTreeValue,
tree: &MergedTree,
@ -166,9 +167,9 @@ pub fn run_mergetool_external(
};
assert_eq!(content.num_sides(), 2);
let files: HashMap<&str, &[u8]> = maplit::hashmap! {
"base" => content.get_remove(0).unwrap().0.as_slice(),
"left" => content.get_add(0).unwrap().0.as_slice(),
"right" => content.get_add(1).unwrap().0.as_slice(),
"base" => content.get_remove(0).unwrap().as_slice(),
"left" => content.get_add(0).unwrap().as_slice(),
"right" => content.get_add(1).unwrap().as_slice(),
"output" => initial_output_content.as_slice(),
};

View file

@ -18,6 +18,7 @@ use std::io::Read;
use std::io::Write;
use std::iter::zip;
use bstr::BString;
use futures::stream::BoxStream;
use futures::try_join;
use futures::Stream;
@ -39,7 +40,6 @@ use crate::copies::CopiesTreeDiffEntryPath;
use crate::diff::Diff;
use crate::diff::DiffHunk;
use crate::files;
use crate::files::ContentHunk;
use crate::files::MergeResult;
use crate::merge::Merge;
use crate::merge::MergeBuilder;
@ -98,7 +98,7 @@ async fn get_file_contents(
store: &Store,
path: &RepoPath,
term: &Option<FileId>,
) -> BackendResult<ContentHunk> {
) -> BackendResult<BString> {
match term {
Some(id) => {
let mut content = vec![];
@ -111,11 +111,11 @@ async fn get_file_contents(
id: id.clone(),
source: err.into(),
})?;
Ok(ContentHunk(content))
Ok(BString::new(content))
}
// If the conflict had removed the file on one side, we pretend that the file
// was empty there.
None => Ok(ContentHunk(vec![])),
None => Ok(BString::new(vec![])),
}
}
@ -123,8 +123,8 @@ pub async fn extract_as_single_hunk(
merge: &Merge<Option<FileId>>,
store: &Store,
path: &RepoPath,
) -> BackendResult<Merge<ContentHunk>> {
let builder: MergeBuilder<ContentHunk> = futures::stream::iter(merge.iter())
) -> BackendResult<Merge<BString>> {
let builder: MergeBuilder<BString> = futures::stream::iter(merge.iter())
.then(|term| get_file_contents(store, path, term))
.try_collect()
.await?;
@ -232,13 +232,13 @@ async fn materialize_tree_value_no_access_denied(
}
pub fn materialize_merge_result(
single_hunk: &Merge<ContentHunk>,
single_hunk: &Merge<BString>,
output: &mut dyn Write,
) -> std::io::Result<()> {
let merge_result = files::merge(single_hunk);
match merge_result {
MergeResult::Resolved(content) => {
output.write_all(&content.0)?;
output.write_all(&content)?;
}
MergeResult::Conflict(hunks) => {
let num_conflicts = hunks
@ -248,7 +248,7 @@ pub fn materialize_merge_result(
let mut conflict_index = 0;
for hunk in hunks {
if let Some(content) = hunk.as_resolved() {
output.write_all(&content.0)?;
output.write_all(content)?;
} else {
conflict_index += 1;
output.write_all(CONFLICT_START_LINE)?;
@ -272,15 +272,15 @@ pub fn materialize_merge_result(
// terms as snapshots.
output.write_all(CONFLICT_MINUS_LINE)?;
output.write_all(format!(" Contents of {base_str}\n").as_bytes())?;
output.write_all(&left.0)?;
output.write_all(left)?;
continue;
};
let diff1 = Diff::by_line([&left.0, &right1.0]).hunks().collect_vec();
let diff1 = Diff::by_line([&left, &right1]).hunks().collect_vec();
// Check if the diff against the next positive term is better. Since
// we want to preserve the order of the terms, we don't match against
// any later positive terms.
if let Some(right2) = hunk.get_add(add_index + 1) {
let diff2 = Diff::by_line([&left.0, &right2.0]).hunks().collect_vec();
let diff2 = Diff::by_line([&left, &right2]).hunks().collect_vec();
if diff_size(&diff2) < diff_size(&diff1) {
// If the next positive term is a better match, emit
// the current positive term as a snapshot and the next
@ -289,7 +289,7 @@ pub fn materialize_merge_result(
output.write_all(
format!(" Contents of side #{}\n", add_index + 1).as_bytes(),
)?;
output.write_all(&right1.0)?;
output.write_all(right1)?;
output.write_all(CONFLICT_DIFF_LINE)?;
output.write_all(
format!(
@ -319,7 +319,7 @@ pub fn materialize_merge_result(
output.write_all(
format!(" Contents of side #{}\n", add_index + 1).as_bytes(),
)?;
output.write_all(&slice.0)?;
output.write_all(slice)?;
}
output.write_all(CONFLICT_END_LINE)?;
output.write_all(
@ -375,7 +375,7 @@ pub fn materialized_diff_stream<'a>(
/// invalid if they don't have the expected arity.
// TODO: "parse" is not usually the opposite of "materialize", so maybe we
// should rename them to "serialize" and "deserialize"?
pub fn parse_conflict(input: &[u8], num_sides: usize) -> Option<Vec<Merge<ContentHunk>>> {
pub fn parse_conflict(input: &[u8], num_sides: usize) -> Option<Vec<Merge<BString>>> {
if input.is_empty() {
return None;
}
@ -395,7 +395,7 @@ pub fn parse_conflict(input: &[u8], num_sides: usize) -> Option<Vec<Merge<Conten
if hunk.num_sides() == num_sides {
let resolved_slice = &input[resolved_start..conflict_start.unwrap()];
if !resolved_slice.is_empty() {
hunks.push(Merge::resolved(ContentHunk(resolved_slice.to_vec())));
hunks.push(Merge::resolved(BString::from(resolved_slice)));
}
hunks.push(hunk);
resolved_start = pos + line.len();
@ -410,15 +410,13 @@ pub fn parse_conflict(input: &[u8], num_sides: usize) -> Option<Vec<Merge<Conten
None
} else {
if resolved_start < input.len() {
hunks.push(Merge::resolved(ContentHunk(
input[resolved_start..].to_vec(),
)));
hunks.push(Merge::resolved(BString::from(&input[resolved_start..])));
}
Some(hunks)
}
}
fn parse_conflict_hunk(input: &[u8]) -> Merge<ContentHunk> {
fn parse_conflict_hunk(input: &[u8]) -> Merge<BString> {
enum State {
Diff,
Minus,
@ -433,18 +431,18 @@ fn parse_conflict_hunk(input: &[u8]) -> Merge<ContentHunk> {
match line[0] {
CONFLICT_DIFF_LINE_CHAR => {
state = State::Diff;
removes.push(ContentHunk(vec![]));
adds.push(ContentHunk(vec![]));
removes.push(BString::new(vec![]));
adds.push(BString::new(vec![]));
continue;
}
CONFLICT_MINUS_LINE_CHAR => {
state = State::Minus;
removes.push(ContentHunk(vec![]));
removes.push(BString::new(vec![]));
continue;
}
CONFLICT_PLUS_LINE_CHAR => {
state = State::Plus;
adds.push(ContentHunk(vec![]));
adds.push(BString::new(vec![]));
continue;
}
_ => {}
@ -453,26 +451,26 @@ fn parse_conflict_hunk(input: &[u8]) -> Merge<ContentHunk> {
match state {
State::Diff => {
if let Some(rest) = line.strip_prefix(b"-") {
removes.last_mut().unwrap().0.extend_from_slice(rest);
removes.last_mut().unwrap().extend_from_slice(rest);
} else if let Some(rest) = line.strip_prefix(b"+") {
adds.last_mut().unwrap().0.extend_from_slice(rest);
adds.last_mut().unwrap().extend_from_slice(rest);
} else if let Some(rest) = line.strip_prefix(b" ") {
removes.last_mut().unwrap().0.extend_from_slice(rest);
adds.last_mut().unwrap().0.extend_from_slice(rest);
removes.last_mut().unwrap().extend_from_slice(rest);
adds.last_mut().unwrap().extend_from_slice(rest);
} else {
// Doesn't look like a conflict
return Merge::resolved(ContentHunk(vec![]));
return Merge::resolved(BString::new(vec![]));
}
}
State::Minus => {
removes.last_mut().unwrap().0.extend_from_slice(line);
removes.last_mut().unwrap().extend_from_slice(line);
}
State::Plus => {
adds.last_mut().unwrap().0.extend_from_slice(line);
adds.last_mut().unwrap().extend_from_slice(line);
}
State::Unknown => {
// Doesn't look like a conflict
return Merge::resolved(ContentHunk(vec![]));
return Merge::resolved(BString::new(vec![]));
}
}
}
@ -526,11 +524,11 @@ pub async fn update_from_content(
for hunk in hunks {
if let Some(slice) = hunk.as_resolved() {
for content in contents.iter_mut() {
content.extend_from_slice(&slice.0);
content.extend_from_slice(slice);
}
} else {
for (content, slice) in zip(contents.iter_mut(), hunk.into_iter()) {
content.extend(slice.0);
content.extend(Vec::from(slice));
}
}
}

View file

@ -16,13 +16,11 @@
use std::borrow::Borrow;
use std::collections::VecDeque;
use std::fmt::Debug;
use std::fmt::Error;
use std::fmt::Formatter;
use std::iter;
use std::mem;
use bstr::BStr;
use bstr::BString;
use crate::diff::Diff;
use crate::diff::DiffHunk;
@ -184,26 +182,10 @@ where
}
}
// TODO: Maybe ContentHunk can be replaced with BString?
#[derive(PartialEq, Eq, Clone)]
pub struct ContentHunk(pub Vec<u8>);
impl AsRef<[u8]> for ContentHunk {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl Debug for ContentHunk {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
String::from_utf8_lossy(&self.0).fmt(f)
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum MergeResult {
Resolved(ContentHunk),
Conflict(Vec<Merge<ContentHunk>>),
Resolved(BString),
Conflict(Vec<Merge<BString>>),
}
pub fn merge<T: AsRef<[u8]>>(slices: &Merge<T>) -> MergeResult {
@ -216,28 +198,24 @@ pub fn merge<T: AsRef<[u8]>>(slices: &Merge<T>) -> MergeResult {
}
fn merge_hunks(diff: &Diff, num_diffs: usize) -> MergeResult {
let mut resolved_hunk = ContentHunk(vec![]);
let mut merge_hunks: Vec<Merge<ContentHunk>> = vec![];
let mut resolved_hunk = BString::new(vec![]);
let mut merge_hunks: Vec<Merge<BString>> = vec![];
for diff_hunk in diff.hunks() {
match diff_hunk {
DiffHunk::Matching(content) => {
resolved_hunk.0.extend_from_slice(content);
resolved_hunk.extend_from_slice(content);
}
DiffHunk::Different(parts) => {
if let Some(resolved) = trivial_merge(&parts[..num_diffs], &parts[num_diffs..]) {
resolved_hunk.0.extend_from_slice(resolved);
resolved_hunk.extend_from_slice(resolved);
} else {
if !resolved_hunk.0.is_empty() {
if !resolved_hunk.is_empty() {
merge_hunks.push(Merge::resolved(resolved_hunk));
resolved_hunk = ContentHunk(vec![]);
resolved_hunk = BString::new(vec![]);
}
merge_hunks.push(Merge::from_removes_adds(
parts[..num_diffs]
.iter()
.map(|part| ContentHunk(part.to_vec())),
parts[num_diffs..]
.iter()
.map(|part| ContentHunk(part.to_vec())),
parts[..num_diffs].iter().copied().map(BString::from),
parts[num_diffs..].iter().copied().map(BString::from),
));
}
}
@ -247,7 +225,7 @@ fn merge_hunks(diff: &Diff, num_diffs: usize) -> MergeResult {
if merge_hunks.is_empty() {
MergeResult::Resolved(resolved_hunk)
} else {
if !resolved_hunk.0.is_empty() {
if !resolved_hunk.is_empty() {
merge_hunks.push(Merge::resolved(resolved_hunk));
}
MergeResult::Conflict(merge_hunks)
@ -258,8 +236,8 @@ fn merge_hunks(diff: &Diff, num_diffs: usize) -> MergeResult {
mod tests {
use super::*;
fn hunk(data: &[u8]) -> ContentHunk {
ContentHunk(data.to_vec())
fn hunk(data: &[u8]) -> BString {
data.into()
}
fn merge(removes: &[&[u8]], adds: &[&[u8]]) -> MergeResult {

View file

@ -471,7 +471,7 @@ pub fn try_resolve_file_conflict(
let merge_result = files::merge(&contents);
match merge_result {
MergeResult::Resolved(merged_content) => {
let id = store.write_file(filename, &mut merged_content.0.as_slice())?;
let id = store.write_file(filename, &mut merged_content.as_slice())?;
Ok(Some(TreeValue::File { id, executable }))
}
MergeResult::Conflict(_) => Ok(None),