1 // Copyright (c) 2006, 2008 Tony Garnock-Jones <tonyg@lshift.net>
2 // Copyright (c) 2006, 2008 LShift Ltd. <query@lshift.net>
4 // Permission is hereby granted, free of charge, to any person
5 // obtaining a copy of this software and associated documentation files
6 // (the "Software"), to deal in the Software without restriction,
7 // including without limitation the rights to use, copy, modify, merge,
8 // publish, distribute, sublicense, and/or sell copies of the Software,
9 // and to permit persons to whom the Software is furnished to do so,
10 // subject to the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 longest_common_subsequence: function(file1, file2) {
26 /* Text diff algorithm following Hunt and McIlroy 1976.
27 * J. W. Hunt and M. D. McIlroy, An algorithm for differential file
28 * comparison, Bell Telephone Laboratories CSTR #41 (1976)
29 * http://www.cs.dartmouth.edu/~doug/
31 * Expects two arrays of strings.
33 var equivalenceClasses;
38 var c, i, j, jX, r, s;
40 equivalenceClasses = {};
41 for (j = 0; j < file2.length; j++) {
43 if (equivalenceClasses[line]) {
44 equivalenceClasses[line].push(j);
46 equivalenceClasses[line] = [j];
50 candidates = [{file1index: -1,
54 for (i = 0; i < file1.length; i++) {
56 file2indices = equivalenceClasses[line] || [];
61 for (jX = 0; jX < file2indices.length; jX++) {
64 for (s = 0; s < candidates.length; s++) {
65 if ((candidates[s].file2index < j) &&
66 ((s == candidates.length - 1) ||
67 (candidates[s + 1].file2index > j)))
71 if (s < candidates.length) {
72 newCandidate = {file1index: i,
74 chain: candidates[s]};
75 if (r == candidates.length) {
82 if (r == candidates.length) {
83 break; // no point in examining further (j)s
91 // At this point, we know the LCS: it's in the reverse of the
92 // linked-list through .chain of
93 // candidates[candidates.length - 1].
95 return candidates[candidates.length - 1];
98 diff_comm: function(file1, file2) {
99 // We apply the LCS to build a "comm"-style picture of the
100 // differences between file1 and file2.
103 var tail1 = file1.length;
104 var tail2 = file2.length;
105 var common = {common: []};
107 function processCommon() {
108 if (common.common.length) {
109 common.common.reverse();
111 common = {common: []};
115 for (var candidate = Diff.longest_common_subsequence(file1, file2);
117 candidate = candidate.chain)
119 var different = {file1: [], file2: []};
121 while (--tail1 > candidate.file1index) {
122 different.file1.push(file1[tail1]);
125 while (--tail2 > candidate.file2index) {
126 different.file2.push(file2[tail2]);
129 if (different.file1.length || different.file2.length) {
131 different.file1.reverse();
132 different.file2.reverse();
133 result.push(different);
137 common.common.push(file1[tail1]);
147 diff_patch: function(file1, file2) {
148 // We apply the LCD to build a JSON representation of a
149 // diff(1)-style patch.
152 var tail1 = file1.length;
153 var tail2 = file2.length;
155 function chunkDescription(file, offset, length) {
157 for (var i = 0; i < length; i++) {
158 chunk.push(file[offset + i]);
160 return {offset: offset,
165 for (var candidate = Diff.longest_common_subsequence(file1, file2);
167 candidate = candidate.chain)
169 var mismatchLength1 = tail1 - candidate.file1index - 1;
170 var mismatchLength2 = tail2 - candidate.file2index - 1;
171 tail1 = candidate.file1index;
172 tail2 = candidate.file2index;
174 if (mismatchLength1 || mismatchLength2) {
175 result.push({file1: chunkDescription(file1,
176 candidate.file1index + 1,
178 file2: chunkDescription(file2,
179 candidate.file2index + 1,
188 strip_patch: function(patch) {
189 // Takes the output of Diff.diff_patch(), and removes
190 // information from it. It can still be used by patch(),
191 // below, but can no longer be inverted.
193 for (var i = 0; i < patch.length; i++) {
194 var chunk = patch[i];
195 newpatch.push({file1: {offset: chunk.file1.offset,
196 length: chunk.file1.length},
197 file2: {chunk: chunk.file2.chunk}});
202 invert_patch: function(patch) {
203 // Takes the output of Diff.diff_patch(), and inverts the
204 // sense of it, so that it can be applied to file2 to give
205 // file1 rather than the other way around.
207 for (var i = 0; i < patch.length; i++) {
208 var chunk = patch[i];
209 var tmp = chunk.file1;
210 chunk.file1 = chunk.file2;
215 patch: function (file, patch) {
216 // Applies a patch to a file.
218 // Given file1 and file2, Diff.patch(file1,
219 // Diff.diff_patch(file1, file2)) should give file2.
222 var commonOffset = 0;
224 function copyCommon(targetOffset) {
225 while (commonOffset < targetOffset) {
226 result.push(file[commonOffset]);
231 for (var chunkIndex = 0; chunkIndex < patch.length; chunkIndex++) {
232 var chunk = patch[chunkIndex];
233 copyCommon(chunk.file1.offset);
234 for (var lineIndex = 0; lineIndex < chunk.file2.chunk.length; lineIndex++) {
235 result.push(chunk.file2.chunk[lineIndex]);
237 commonOffset += chunk.file1.length;
240 copyCommon(file.length);
244 diff_indices: function(file1, file2) {
245 // We apply the LCS to give a simple representation of the
246 // offsets and lengths of mismatched chunks in the input
247 // files. This is used by diff3_merge_indices below.
250 var tail1 = file1.length;
251 var tail2 = file2.length;
253 for (var candidate = Diff.longest_common_subsequence(file1, file2);
255 candidate = candidate.chain)
257 var mismatchLength1 = tail1 - candidate.file1index - 1;
258 var mismatchLength2 = tail2 - candidate.file2index - 1;
259 tail1 = candidate.file1index;
260 tail2 = candidate.file2index;
262 if (mismatchLength1 || mismatchLength2) {
263 result.push({file1: [tail1 + 1, mismatchLength1],
264 file2: [tail2 + 1, mismatchLength2]});
272 diff3_merge_indices: function (a, o, b) {
273 // Given three files, A, O, and B, where both A and B are
274 // independently derived from O, returns a fairly complicated
275 // internal representation of merge decisions it's taken. The
276 // interested reader may wish to consult
278 // Sanjeev Khanna, Keshav Kunal, and Benjamin C. Pierce. "A
279 // Formal Investigation of Diff3." In Arvind and Prasad,
280 // editors, Foundations of Software Technology and Theoretical
281 // Computer Science (FSTTCS), December 2007.
283 // (http://www.cis.upenn.edu/~bcpierce/papers/diff3-short.pdf)
286 var m1 = Diff.diff_indices(o, a);
287 var m2 = Diff.diff_indices(o, b);
290 function addHunk(h, side) {
291 hunks.push([h.file1[0], side, h.file1[1], h.file2[0], h.file2[1]]);
293 for (i = 0; i < m1.length; i++) { addHunk(m1[i], 0); }
294 for (i = 0; i < m2.length; i++) { addHunk(m2[i], 2); }
298 var commonOffset = 0;
299 function copyCommon(targetOffset) {
300 if (targetOffset > commonOffset) {
301 result.push([1, commonOffset, targetOffset - commonOffset]);
302 commonOffset = targetOffset;
306 for (var hunkIndex = 0; hunkIndex < hunks.length; hunkIndex++) {
307 var firstHunkIndex = hunkIndex;
308 var hunk = hunks[hunkIndex];
309 var regionLhs = hunk[0];
310 var regionRhs = regionLhs + hunk[2];
311 while (hunkIndex < hunks.length - 1) {
312 var maybeOverlapping = hunks[hunkIndex + 1];
313 var maybeLhs = maybeOverlapping[0];
314 if (maybeLhs > regionRhs) break;
315 regionRhs = maybeLhs + maybeOverlapping[2];
319 copyCommon(regionLhs);
320 if (firstHunkIndex == hunkIndex) {
321 // The "overlap" was only one hunk long, meaning that
322 // there's no conflict here. Either a and o were the
323 // same, or b and o were the same.
325 result.push([hunk[1], hunk[3], hunk[4]]);
328 // A proper conflict. Determine the extents of the
329 // regions involved from a, o and b. Effectively merge
330 // all the hunks on the left into one giant hunk, and
331 // do the same for the right; then, correct for skew
332 // in the regions of o that each side changed, and
333 // report appropriate spans for the three sides.
335 0: [a.length, -1, o.length, -1],
336 2: [b.length, -1, o.length, -1]
338 for (i = firstHunkIndex; i <= hunkIndex; i++) {
341 var r = regions[side];
343 var oRhs = oLhs + hunk[2];
345 var abRhs = abLhs + hunk[4];
346 r[0] = Math.min(abLhs, r[0]);
347 r[1] = Math.max(abRhs, r[1]);
348 r[2] = Math.min(oLhs, r[2]);
349 r[3] = Math.max(oRhs, r[3]);
351 var aLhs = regions[0][0] + (regionLhs - regions[0][2]);
352 var aRhs = regions[0][1] + (regionRhs - regions[0][3]);
353 var bLhs = regions[2][0] + (regionLhs - regions[2][2]);
354 var bRhs = regions[2][1] + (regionRhs - regions[2][3]);
357 regionLhs, regionRhs - regionLhs,
360 commonOffset = regionRhs;
363 copyCommon(o.length);
367 diff3_merge: function (a, o, b, excludeFalseConflicts) {
368 // Applies the output of Diff.diff3_merge_indices to actually
369 // construct the merged file; the returned result alternates
370 // between "ok" and "conflict" blocks.
373 var files = [a, o, b];
374 var indices = Diff.diff3_merge_indices(a, o, b);
378 if (okLines.length) {
379 result.push({ok: okLines});
383 function pushOk(xs) {
384 for (var j = 0; j < xs.length; j++) {
389 function isTrueConflict(rec) {
390 if (rec[2] != rec[6]) return true;
393 for (var j = 0; j < rec[2]; j++) {
394 if (a[j + aoff] != b[j + boff]) return true;
399 for (var i = 0; i < indices.length; i++) {
403 if (excludeFalseConflicts && !isTrueConflict(x)) {
404 pushOk(files[0].slice(x[1], x[1] + x[2]));
407 result.push({conflict: {a: a.slice(x[1], x[1] + x[2]),
409 o: o.slice(x[3], x[3] + x[4]),
411 b: b.slice(x[5], x[5] + x[6]),
415 pushOk(files[side].slice(x[1], x[1] + x[2]));