git/
diff_line.rs

1use crate::Origin;
2
3/// Represents a single line in a diff
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct DiffLine {
6	end_of_file: bool,
7	line: String,
8	new_line_number: Option<u32>,
9	old_line_number: Option<u32>,
10	origin: Origin,
11}
12
13impl DiffLine {
14	/// Create a new `DiffLine`.
15	#[inline]
16	#[must_use]
17	pub fn new(
18		origin: Origin,
19		line: &str,
20		old_line_number: Option<u32>,
21		new_line_number: Option<u32>,
22		end_of_file: bool,
23	) -> Self {
24		Self {
25			end_of_file,
26			// remove the end of file marker from diff
27			line: if end_of_file {
28				line.replace("\n\\ No newline at end of file\n", "")
29			}
30			else {
31				String::from(line)
32			},
33			new_line_number,
34			old_line_number,
35			origin,
36		}
37	}
38
39	/// Get the `Origin` of the `DiffLine`
40	#[inline]
41	#[must_use]
42	pub const fn origin(&self) -> Origin {
43		self.origin
44	}
45
46	/// Get the line of the `DiffLine`.
47	#[inline]
48	#[must_use]
49	pub fn line(&self) -> &str {
50		self.line.as_str()
51	}
52
53	/// Get the old line number of the `DiffLine`, if it exists, else `None`.
54	#[inline]
55	#[must_use]
56	pub const fn old_line_number(&self) -> Option<u32> {
57		self.old_line_number
58	}
59
60	/// Get the new line number of the `DiffLine`, if it exists, else `None`.
61	#[inline]
62	#[must_use]
63	pub const fn new_line_number(&self) -> Option<u32> {
64		self.new_line_number
65	}
66
67	/// Returns `true` is this line was at the end of a file, else `false`.
68	#[inline]
69	#[must_use]
70	pub const fn end_of_file(&self) -> bool {
71		self.end_of_file
72	}
73
74	pub(crate) fn from(diff_line: &git2::DiffLine<'_>) -> Self {
75		Self::new(
76			Origin::from(diff_line.origin_value()),
77			std::str::from_utf8(diff_line.content()).unwrap_or("<INVALID UTF8>"),
78			diff_line.old_lineno(),
79			diff_line.new_lineno(),
80			diff_line.origin_value() == git2::DiffLineType::ContextEOFNL
81				|| diff_line.origin_value() == git2::DiffLineType::AddEOFNL
82				|| diff_line.origin_value() == git2::DiffLineType::DeleteEOFNL,
83		)
84	}
85}
86
87#[cfg(test)]
88mod tests {
89	use claim::assert_some_eq;
90	use parking_lot::Mutex;
91
92	use super::*;
93
94	fn create_diff_line() -> DiffLine {
95		DiffLine::new(Origin::Addition, "Line", Some(1), Some(2), false)
96	}
97
98	#[test]
99	fn origin() {
100		assert_eq!(create_diff_line().origin(), Origin::Addition);
101	}
102
103	#[test]
104	fn line() {
105		assert_eq!(create_diff_line().line(), "Line");
106	}
107
108	#[test]
109	fn old_line_number() {
110		assert_some_eq!(create_diff_line().old_line_number(), 1);
111	}
112
113	#[test]
114	fn new_line_number() {
115		assert_some_eq!(create_diff_line().new_line_number(), 2);
116	}
117
118	#[test]
119	fn end_of_file() {
120		assert!(!create_diff_line().end_of_file());
121	}
122
123	#[test]
124	fn new_without_end_of_file() {
125		let diff_line = DiffLine::new(
126			Origin::Addition,
127			"This is a line\n\\ No newline at end of file\n",
128			None,
129			None,
130			false,
131		);
132		assert_eq!(diff_line.line(), "This is a line\n\\ No newline at end of file\n");
133		assert!(!diff_line.end_of_file());
134	}
135
136	#[test]
137	fn new_with_end_of_file() {
138		let diff_line = DiffLine::new(
139			Origin::Addition,
140			"This is a line\n\\ No newline at end of file\n",
141			None,
142			None,
143			true,
144		);
145		assert_eq!(diff_line.line(), "This is a line");
146		assert!(diff_line.end_of_file());
147	}
148
149	#[test]
150	fn from_diff_lines() {
151		let diff = git2::Diff::from_buffer(
152			[
153				"diff --git a/file.rs b/file.rs",
154				"index 493c0dd..4e07a6e 100644",
155				"--- a/file.rs",
156				"+++ b/file.rs",
157				"@@ -1,2 +1,2 @@ context",
158				" context",
159				"-deleted",
160				"+added",
161				"",
162			]
163			.join("\n")
164			.as_bytes(),
165		)
166		.unwrap();
167
168		let lines = Mutex::new(vec![]);
169		diff.print(git2::DiffFormat::Patch, |_, _, diff_line| {
170			lines.lock().push(DiffLine::from(&diff_line));
171			true
172		})
173		.unwrap();
174
175		let file_header = [
176			"diff --git a/file.rs b/file.rs",
177			"index 493c0dd..4e07a6e 100644",
178			"--- a/file.rs",
179			"+++ b/file.rs",
180			"",
181		]
182		.join("\n");
183		let expected = [
184			DiffLine::new(Origin::Header, file_header.as_str(), None, None, false),
185			DiffLine::new(Origin::Header, "@@ -1,2 +1,2 @@ context\n", None, None, false),
186			DiffLine::new(Origin::Context, "context\n", Some(1), Some(1), false),
187			DiffLine::new(Origin::Deletion, "deleted\n", Some(2), None, false),
188			DiffLine::new(Origin::Addition, "added\n", None, Some(2), false),
189		];
190		assert_eq!(lines.into_inner(), expected);
191	}
192}