1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
//! Tools for finding and manipulating differences between files
//!
//! ## Overview
//!
//! This library is intended to be a collection of tools used to find and
//! manipulate differences between files inspired by [LibXDiff] and [GNU
//! Diffutils]. Version control systems like [Git] and [Mercurial] generally
//! communicate differences between two versions of a file using a `diff` or
//! `patch`.
//!
//! The current diff implementation is based on the [Myers' diff algorithm].
//!
//! The documentation generally refers to "files" in many places but none of
//! the apis explicitly operate on on-disk files. Instead this library
//! requires that the text being operated on resides in-memory and as such if
//! you want to perform operations on files, it is up to the user to load the
//! contents of those files into memory before passing their contents to the
//! apis provided by this library.
//!
//! ## Cargo Feature Flags
//!
//! This crate is `no_std` by default.
//! Enable [Cargo features] as needed:
//!
//! - `std` for std::io::Write-based formatting impls
//! - `color` for ANSI-colored patch formatting
//! - `binary` for applying parsed git binary patches
//!
//! ## UTF-8 and Non-UTF-8
//!
//! This library has support for working with both utf8 and non-utf8 texts.
//! Most of the API's have two different variants, one for working with utf8
//! `str` texts (e.g. [`create_patch`]) and one for working with bytes `[u8]`
//! which may or may not be utf8 (e.g. [`create_patch_bytes`]).
//!
//! ## Creating a Patch
//!
//! A [`Patch`] between two texts can be created by doing the following:
//!
//! ```
//! use diffy::create_patch;
//!
//! let original = "The Way of Kings\nWords of Radiance\n";
//! let modified = "The Way of Kings\nWords of Radiance\nOathbringer\n";
//!
//! let patch = create_patch(original, modified);
//! #
//! # let expected = "\
//! # --- original
//! # +++ modified
//! # @@ -1,2 +1,3 @@
//! # The Way of Kings
//! # Words of Radiance
//! # +Oathbringer
//! # ";
//! #
//! # assert_eq!(patch.to_string(), expected);
//! ```
//!
//! A [`Patch`] can the be output in the [Unified Format] either by using its
//! [`Display`] impl or by using a [`PatchFormatter`] to output the diff with
//! color (requires the `color` feature).
//!
//! ```
//! # use diffy::create_patch;
//! #
//! # let original = "The Way of Kings\nWords of Radiance\n";
//! # let modified = "The Way of Kings\nWords of Radiance\nOathbringer\n";
//! #
//! # let patch = create_patch(original, modified);
//! #
//! # let expected = "\
//! # --- original
//! # +++ modified
//! # @@ -1,2 +1,3 @@
//! # The Way of Kings
//! # Words of Radiance
//! # +Oathbringer
//! # ";
//! #
//! # assert_eq!(patch.to_string(), expected);
//! #
//! // Without color
//! print!("{}", patch);
//! ```
//!
//! With the `color` feature enabled:
//!
//! ```ignore
//! use diffy::PatchFormatter;
//! let f = PatchFormatter::new().with_color();
//! print!("{}", f.fmt_patch(&patch));
//! ```
//!
//! ```console
//! --- original
//! +++ modified
//! @@ -1,2 +1,3 @@
//! The Way of Kings
//! Words of Radiance
//! +Oathbringer
//! ```
//!
//! ## Applying a Patch
//!
//! Once you have a [`Patch`] you can apply it to a base image in order to
//! recover the new text. Each hunk will be applied to the base image in
//! sequence. Similarly to GNU `patch`, this implementation can detect when
//! line numbers specified in the patch are incorrect and will attempt to find
//! the correct place to apply each hunk by iterating forward and backward
//! from the given position until all context lines from a hunk match the base
//! image.
//!
//! ```
//! use diffy::Patch;
//! use diffy::apply;
//!
//! let s = "\
//! --- a/skybreaker-ideals
//! +++ b/skybreaker-ideals
//! @@ -10,6 +10,8 @@
//! First:
//! Life before death,
//! strength before weakness,
//! journey before destination.
//! Second:
//! - I will put the law before all else.
//! + I swear to seek justice,
//! + to let it guide me,
//! + until I find a more perfect Ideal.
//! ";
//!
//! let patch = Patch::from_str(s).unwrap();
//!
//! let base_image = "\
//! First:
//! Life before death,
//! strength before weakness,
//! journey before destination.
//! Second:
//! I will put the law before all else.
//! ";
//!
//! let expected = "\
//! First:
//! Life before death,
//! strength before weakness,
//! journey before destination.
//! Second:
//! I swear to seek justice,
//! to let it guide me,
//! until I find a more perfect Ideal.
//! ";
//!
//! assert_eq!(apply(base_image, &patch).unwrap(), expected);
//! ```
//!
//! ## Parsing Multi-File Patches
//!
//! The [`patch_set`] module provides support for parsing unified diffs
//! that contain changes to multiple files,
//! such as `git diff` and `git format-patch` output.
//! [`PatchSet`] is a streaming iterator,
//! so callers can process file patches one at a time.
//!
//! Use [`ParseOptions::gitdiff`] for git-style diffs or
//! [`ParseOptions::unidiff`] for plain unified diffs.
//!
//! ```
//! use diffy::apply;
//! use diffy::patch_set::FileOperation;
//! use diffy::patch_set::ParseOptions;
//! use diffy::patch_set::PatchSet;
//!
//! let input = "\
//! diff --git a/alpha.txt b/alpha.txt
//! index 1111111..2222222 100644
//! --- a/alpha.txt
//! +++ b/alpha.txt
//! @@ -1 +1 @@
//! -alpha
//! +ALPHA
//! diff --git a/beta.txt b/beta.txt
//! new file mode 100644
//! --- /dev/null
//! +++ b/beta.txt
//! @@ -0,0 +1 @@
//! +beta
//! ";
//!
//! let mut patches = PatchSet::parse(input, ParseOptions::gitdiff());
//!
//! let first = patches.next().unwrap().unwrap();
//! let second = patches.next().unwrap().unwrap();
//! assert!(patches.next().is_none());
//!
//! match first.operation().strip_prefix(1) {
//! FileOperation::Modify { original, modified } => {
//! assert_eq!(original, "alpha.txt");
//! assert_eq!(modified, "alpha.txt");
//! }
//! operation => panic!("unexpected operation: {operation:?}"),
//! }
//!
//! let text_patch = first.patch().as_text().unwrap();
//! assert_eq!(apply("alpha\n", text_patch).unwrap(), "ALPHA\n");
//!
//! match second.operation().strip_prefix(1) {
//! FileOperation::Create(path) => assert_eq!(path, "beta.txt"),
//! operation => panic!("unexpected operation: {operation:?}"),
//! }
//! ```
//!
//! With the `binary` Cargo feature enabled,
//! parsed multi-file patches can also contain [`BinaryPatch`] values.
//! You can apply them with [`BinaryPatch::apply`].
//!
//! ## Performing a Three-way Merge
//!
//! Two files `A` and `B` can be merged together given a common ancestor or
//! original file `O` to produce a file `C` similarly to how [diff3]
//! performs a three-way merge.
//!
//! ```console
//! --- A ---
//! / \
//! / \
//! O C
//! \ /
//! \ /
//! --- B ---
//! ```
//!
//! If files `A` and `B` modified different regions of the original file `O`
//! (or the same region in the same way) then the files can be merged without
//! conflict.
//!
//! ```
//! use diffy::merge;
//!
//! let original = "the final empire\nThe Well of Ascension\nThe hero of ages\n";
//! let a = "The Final Empire\nThe Well of Ascension\nThe Hero of Ages\n";
//! let b = "The Final Empire\nThe Well of Ascension\nThe hero of ages\n";
//! let expected = "\
//! The Final Empire
//! The Well of Ascension
//! The Hero of Ages
//! ";
//!
//! assert_eq!(merge(original, a, b).unwrap(), expected);
//! ```
//!
//! If both files `A` and `B` modified the same region of the original file
//! `O` (and those modifications are different), it would result in a conflict
//! as it is not clear which modifications should be used in the merged
//! result.
//!
//! ```
//! use diffy::merge;
//!
//! let original = "The Final Empire\nThe Well of Ascension\nThe hero of ages\n";
//! let a = "The Final Empire\nThe Well of Ascension\nThe Hero of Ages\nSecret History\n";
//! let b = "The Final Empire\nThe Well of Ascension\nThe hero of ages\nThe Alloy of Law\n";
//! let expected = "\
//! The Final Empire
//! The Well of Ascension
//! <<<<<<< ours
//! The Hero of Ages
//! Secret History
//! ||||||| original
//! The hero of ages
//! =======
//! The hero of ages
//! The Alloy of Law
//! >>>>>>> theirs
//! ";
//!
//! assert_eq!(merge(original, a, b).unwrap_err(), expected);
//! ```
//!
//! [LibXDiff]: http://www.xmailserver.org/xdiff-lib.html
//! [Myers' diff algorithm]: http://www.xmailserver.org/diff2.pdf
//! [GNU Diffutils]: https://www.gnu.org/software/diffutils/
//! [Git]: https://git-scm.com/
//! [Mercurial]: https://www.mercurial-scm.org/
//! [Unified Format]: https://en.wikipedia.org/wiki/Diff#Unified_format
//! [diff3]: https://en.wikipedia.org/wiki/Diff3
//! [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html
//!
//! [`BinaryPatch`]: crate::binary::BinaryPatch
//! [`BinaryPatch::apply`]: crate::binary::BinaryPatch::apply
//! [`Display`]: core::fmt::Display
//! [`ParseOptions::gitdiff`]: crate::patch_set::ParseOptions::gitdiff
//! [`ParseOptions::unidiff`]: crate::patch_set::ParseOptions::unidiff
//! [`Patch`]: crate::Patch
//! [`PatchFormatter`]: crate::PatchFormatter
//! [`PatchKind::as_binary`]: crate::patch_set::PatchKind::as_binary
//! [`PatchSet`]: crate::patch_set::PatchSet
//! [`PatchSet::parse`]: crate::patch_set::PatchSet::parse
//! [`PatchSet::parse_bytes`]: crate::patch_set::PatchSet::parse_bytes
//! [`create_patch`]: crate::create_patch
//! [`create_patch_bytes`]: crate::create_patch_bytes
//! [`patch_set`]: crate::patch_set
// unconditionally define as no_std to have consistency on the prelude that is auto imported.
extern crate alloc;
extern crate std;
pub use ApplyError;
pub use apply;
pub use apply_bytes;
pub use DiffOptions;
pub use create_patch;
pub use create_patch_bytes;
pub use ConflictStyle;
pub use MergeOptions;
pub use merge;
pub use merge_bytes;
pub use Hunk;
pub use HunkRange;
pub use Line;
pub use ParsePatchError;
pub use Patch;
pub use PatchFormatter;