Skip to content

Commit 5505646

Browse files
committed
Make GitDiff compatible to strings and bytes.
1 parent 5e699d2 commit 5505646

File tree

2 files changed

+141
-55
lines changed

2 files changed

+141
-55
lines changed

gix-diff/src/blob/git_diff.rs

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
//! Facilities to produce git-formatted diffs.
22
3-
use std::cmp::Ordering;
4-
use std::ops::Range;
5-
63
use crate::blob::GitDiff;
4+
use bstr::ByteSlice;
75
use imara_diff::intern::{InternedInput, Interner, Token};
86
use imara_diff::Sink;
7+
use std::cmp::Ordering;
8+
use std::ops::Range;
99

1010
// Explanation for the following numbers can be found here:
1111
// https://github.com/git/git/blob/324fbaab88126196bd42e7fa383ee94e165d61b5/xdiff/xdiffi.c#L535
@@ -29,9 +29,11 @@ pub(super) mod types {
2929
use crate::blob::git_diff::ChangeGroup;
3030

3131
/// A [`Sink`](imara_diff::Sink) that creates a diff like git would.
32+
///
33+
/// See the [diff slider repository](https://github.com/mhagger/diff-slider-tools) for more information.
3234
pub struct GitDiff<'a, T>
3335
where
34-
T: std::fmt::Display,
36+
T: AsRef<[u8]>,
3537
{
3638
pub(crate) after: &'a [imara_diff::intern::Token],
3739
pub(crate) interner: &'a imara_diff::intern::Interner<T>,
@@ -75,7 +77,7 @@ impl PartialOrd for Score {
7577
#[derive(PartialEq, Debug)]
7678
pub struct ChangeGroup {
7779
/// Range indicating the lines of the previous block.
78-
/// To actually see how the previous block looked like, you need to combine this range with
80+
/// To actually see what the previous block looked like, you need to combine this range with
7981
/// the [`InternedInput`].
8082
pub before: Range<usize>,
8183
/// Range indicating the lines of the new block
@@ -86,16 +88,28 @@ pub struct ChangeGroup {
8688
pub change_kind: ChangeKind,
8789
}
8890

89-
// Calculate the indentation of a single line
90-
fn get_indent(s: String) -> Option<u8> {
91+
impl ChangeGroup {
92+
/// Return [before](Self::before) and [after](Self::after) as `u32` ranges for use in [Sink::process_change()].
93+
///
94+
/// This is useful for creating [unified diffs](crate::blob::UnifiedDiff), for example.
95+
pub fn as_u32_ranges(&self) -> (Range<u32>, Range<u32>) {
96+
(
97+
self.before.start as u32..self.before.end as u32,
98+
self.after.start as u32..self.after.end as u32,
99+
)
100+
}
101+
}
102+
103+
// Calculate the indentation of a single line as number of tabs.
104+
fn get_indent(s: &[u8]) -> Option<u8> {
91105
let mut indent = 0;
92106

93-
for char in s.chars() {
94-
if !char.is_whitespace() {
107+
for char in s.bytes() {
108+
if !char.is_ascii_whitespace() {
95109
return Some(indent);
96-
} else if char == ' ' {
110+
} else if char == b' ' {
97111
indent += 1;
98-
} else if char == '\t' {
112+
} else if char == b'\t' {
99113
indent += 8 - indent % 8;
100114
}
101115

@@ -107,26 +121,21 @@ fn get_indent(s: String) -> Option<u8> {
107121
None
108122
}
109123

110-
fn measure_and_score_change<T: std::fmt::Display>(
111-
lines: &[Token],
112-
split: usize,
113-
interner: &Interner<T>,
114-
score: &mut Score,
115-
) {
124+
fn measure_and_score_change<T: AsRef<[u8]>>(lines: &[Token], split: usize, interner: &Interner<T>, score: &mut Score) {
116125
// Gather information about the surroundings of the change
117126
let end_of_file = split >= lines.len();
118127
let mut indent: Option<u8> = if split >= lines.len() {
119128
None
120129
} else {
121-
get_indent(interner[lines[split]].to_string())
130+
get_indent(interner[lines[split]].as_ref())
122131
};
123132
let mut pre_blank = 0;
124133
let mut pre_indent: Option<u8> = None;
125134
let mut post_blank = 0;
126135
let mut post_indent: Option<u8> = None;
127136

128137
for line in (0..=split.saturating_sub(1)).rev() {
129-
pre_indent = get_indent(interner[lines[line]].to_string());
138+
pre_indent = get_indent(interner[lines[line]].as_ref());
130139
if pre_indent.is_none() {
131140
pre_blank += 1;
132141
if pre_blank == MAX_BLANKS {
@@ -136,7 +145,7 @@ fn measure_and_score_change<T: std::fmt::Display>(
136145
}
137146
}
138147
for line in split + 1..lines.len() {
139-
post_indent = get_indent(interner[lines[line]].to_string());
148+
post_indent = get_indent(interner[lines[line]].as_ref());
140149
if post_indent.is_none() {
141150
post_blank += 1;
142151
if post_blank == MAX_BLANKS {
@@ -191,7 +200,7 @@ fn measure_and_score_change<T: std::fmt::Display>(
191200

192201
impl<'a, T> GitDiff<'a, T>
193202
where
194-
T: std::fmt::Display,
203+
T: AsRef<[u8]>,
195204
{
196205
/// Create a new instance of [`GitDiff`] that can then be passed to [`imara_diff::diff`]
197206
/// and generate a more human-readable diff.
@@ -206,7 +215,7 @@ where
206215

207216
impl<T> Sink for GitDiff<'_, T>
208217
where
209-
T: std::fmt::Display,
218+
T: AsRef<[u8]>,
210219
{
211220
type Out = Vec<ChangeGroup>;
212221

gix-diff/tests/diff/blob/git_diff.rs

Lines changed: 110 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use gix_diff::blob::intern::InternedInput;
2+
use gix_diff::blob::unified_diff::{ContextSize, NewlineSeparator};
23
use gix_diff::blob::{
34
git_diff::{ChangeGroup, ChangeKind},
4-
Algorithm, GitDiff,
5+
Algorithm, GitDiff, Sink, UnifiedDiff,
56
};
7+
use std::hash::Hash;
68

79
#[test]
8-
fn basic() {
10+
fn basic() -> crate::Result {
911
let before = r#"struct SomeStruct {
1012
field1: f64,
1113
field2: f64,
@@ -52,36 +54,111 @@ fn get_field2(c: &SomeStruct) -> f64 {
5254
"#;
5355
use crate::blob::git_diff::ChangeKind;
5456

57+
let expected = &[
58+
ChangeGroup {
59+
before: 0..0,
60+
after: 0..1,
61+
change_kind: ChangeKind::Added,
62+
},
63+
ChangeGroup {
64+
before: 6..7,
65+
after: 7..7,
66+
change_kind: ChangeKind::RemovedBelow,
67+
},
68+
ChangeGroup {
69+
before: 10..12,
70+
after: 10..12,
71+
change_kind: ChangeKind::Modified,
72+
},
73+
ChangeGroup {
74+
before: 13..13,
75+
after: 13..14,
76+
change_kind: ChangeKind::Added,
77+
},
78+
ChangeGroup {
79+
before: 17..17,
80+
after: 19..23,
81+
change_kind: ChangeKind::Added,
82+
},
83+
];
84+
5585
let input = InternedInput::new(before, after);
56-
let diff = gix_diff::blob::diff(Algorithm::Histogram, &input, GitDiff::new(&input));
57-
assert_eq!(
58-
diff,
59-
vec![
60-
ChangeGroup {
61-
before: 0..0,
62-
after: 0..1,
63-
change_kind: ChangeKind::Added
64-
},
65-
ChangeGroup {
66-
before: 6..7,
67-
after: 7..7,
68-
change_kind: ChangeKind::RemovedBelow
69-
},
70-
ChangeGroup {
71-
before: 10..12,
72-
after: 10..12,
73-
change_kind: ChangeKind::Modified
74-
},
75-
ChangeGroup {
76-
before: 13..13,
77-
after: 13..14,
78-
change_kind: ChangeKind::Added
79-
},
80-
ChangeGroup {
81-
before: 17..17,
82-
after: 19..23,
83-
change_kind: ChangeKind::Added
84-
}
85-
]
86-
);
86+
let actual = gix_diff::blob::diff(Algorithm::Histogram, &input, GitDiff::new(&input));
87+
assert_eq!(actual, expected);
88+
insta::assert_snapshot!(uni_diff_string(&input, actual), @r#"
89+
@@ -1,1 +1,2 @@
90+
+/// This is a struct
91+
struct SomeStruct {
92+
@@ -6,3 +7,2 @@
93+
fn main() {
94+
- // Some comment
95+
let c = SomeStruct { field1: 10.0, field2: 10.0 };
96+
@@ -10,5 +10,6 @@
97+
println!(
98+
- "Print field1 from SomeStruct {}",
99+
- get_field1(&c)
100+
+ "Print field1 and field2 from SomeStruct {} {}",
101+
+ get_field1(&c), get_field2(&c)
102+
);
103+
+ println!("Print another line");
104+
}
105+
@@ -17,2 +19,6 @@
106+
c.field1
107+
+
108+
+fn get_field2(c: &SomeStruct) -> f64 {
109+
+ c.field2
110+
+}
111+
}
112+
"#);
113+
114+
let standard_slider = gix_diff::blob::diff(Algorithm::Histogram, &input, uni_diff(&input))?;
115+
insta::assert_snapshot!(standard_slider, @r#"
116+
@@ -1,1 +1,2 @@
117+
+/// This is a struct
118+
struct SomeStruct {
119+
@@ -6,3 +7,2 @@
120+
fn main() {
121+
- // Some comment
122+
let c = SomeStruct { field1: 10.0, field2: 10.0 };
123+
@@ -10,5 +10,6 @@
124+
println!(
125+
- "Print field1 from SomeStruct {}",
126+
- get_field1(&c)
127+
+ "Print field1 and field2 from SomeStruct {} {}",
128+
+ get_field1(&c), get_field2(&c)
129+
);
130+
+ println!("Print another line");
131+
}
132+
@@ -17,2 +18,6 @@
133+
c.field1
134+
+}
135+
+
136+
+fn get_field2(c: &SomeStruct) -> f64 {
137+
+ c.field2
138+
}
139+
"#);
140+
141+
let input = InternedInput::new(before.as_bytes(), after.as_bytes());
142+
let actual = gix_diff::blob::diff(Algorithm::Histogram, &input, GitDiff::new(&input));
143+
assert_eq!(actual, expected);
144+
145+
Ok(())
146+
}
147+
148+
fn uni_diff<T: Eq + Hash + AsRef<[u8]>>(input: &InternedInput<T>) -> UnifiedDiff<'_, T, String> {
149+
UnifiedDiff::new(
150+
input,
151+
String::default(),
152+
NewlineSeparator::AfterHeaderAndLine("\n"),
153+
ContextSize::symmetrical(1),
154+
)
155+
}
156+
157+
fn uni_diff_string<T: Eq + Hash + AsRef<[u8]>>(input: &InternedInput<T>, changes: Vec<ChangeGroup>) -> String {
158+
let mut uni = uni_diff(input);
159+
for change in changes {
160+
let (before, after) = change.as_u32_ranges();
161+
uni.process_change(before, after);
162+
}
163+
uni.finish().expect("in-memory is infallible")
87164
}

0 commit comments

Comments
 (0)