Followup to r29625: fix getopt tests.
[svn.git] / subversion / libsvn_diff / diff3.c
blobd3b91eb5577087cf818d45449269f7ac9b095fb5
1 /*
2 * diff3.c : routines for doing diffs
4 * ====================================================================
5 * Copyright (c) 2000-2004 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
20 #include <apr.h>
21 #include <apr_pools.h>
22 #include <apr_general.h>
24 #include "svn_pools.h"
25 #include "svn_error.h"
26 #include "svn_diff.h"
27 #include "svn_types.h"
29 #include "diff.h"
32 void
33 svn_diff__resolve_conflict(svn_diff_t *hunk,
34 svn_diff__position_t **position_list1,
35 svn_diff__position_t **position_list2,
36 apr_pool_t *pool)
38 apr_off_t modified_start = hunk->modified_start + 1;
39 apr_off_t latest_start = hunk->latest_start + 1;
40 apr_off_t common_length;
41 apr_off_t modified_length = hunk->modified_length;
42 apr_off_t latest_length = hunk->latest_length;
43 svn_diff__position_t *start_position[2];
44 svn_diff__position_t *position[2];
45 svn_diff__lcs_t *lcs = NULL;
46 svn_diff__lcs_t **lcs_ref = &lcs;
47 svn_diff_t **diff_ref = &hunk->resolved_diff;
48 apr_pool_t *subpool;
50 /* First find the starting positions for the
51 * comparison
54 start_position[0] = *position_list1;
55 start_position[1] = *position_list2;
57 while (start_position[0]->offset < modified_start)
58 start_position[0] = start_position[0]->next;
60 while (start_position[1]->offset < latest_start)
61 start_position[1] = start_position[1]->next;
63 position[0] = start_position[0];
64 position[1] = start_position[1];
66 common_length = modified_length < latest_length
67 ? modified_length : latest_length;
69 while (common_length > 0
70 && position[0]->node == position[1]->node)
72 position[0] = position[0]->next;
73 position[1] = position[1]->next;
75 common_length--;
78 if (common_length == 0
79 && modified_length == latest_length)
81 hunk->type = svn_diff__type_diff_common;
82 hunk->resolved_diff = NULL;
84 *position_list1 = position[0];
85 *position_list2 = position[1];
87 return;
90 hunk->type = svn_diff__type_conflict;
92 /* ### If we have a conflict we can try to find the
93 * ### common parts in it by getting an lcs between
94 * ### modified (start to start + length) and
95 * ### latest (start to start + length).
96 * ### We use this lcs to create a simple diff. Only
97 * ### where there is a diff between the two, we have
98 * ### a conflict.
99 * ### This raises a problem; several common diffs and
100 * ### conflicts can occur within the same original
101 * ### block. This needs some thought.
102 * ###
103 * ### NB: We can use the node _pointers_ to identify
104 * ### different tokens
107 subpool = svn_pool_create(pool);
109 /* Calculate how much of the two sequences was
110 * actually the same.
112 common_length = (modified_length < latest_length
113 ? modified_length : latest_length)
114 - common_length;
116 /* If there were matching symbols at the start of
117 * both sequences, record that fact.
119 if (common_length > 0)
121 lcs = apr_palloc(subpool, sizeof(*lcs));
122 lcs->next = NULL;
123 lcs->position[0] = start_position[0];
124 lcs->position[1] = start_position[1];
125 lcs->length = common_length;
127 lcs_ref = &lcs->next;
130 modified_length -= common_length;
131 latest_length -= common_length;
133 modified_start = start_position[0]->offset;
134 latest_start = start_position[1]->offset;
136 start_position[0] = position[0];
137 start_position[1] = position[1];
139 /* Create a new ring for svn_diff__lcs to grok.
140 * We can safely do this given we don't need the
141 * positions we processed anymore.
143 if (modified_length == 0)
145 *position_list1 = position[0];
146 position[0] = NULL;
148 else
150 while (--modified_length)
151 position[0] = position[0]->next;
153 *position_list1 = position[0]->next;
154 position[0]->next = start_position[0];
157 if (latest_length == 0)
159 *position_list2 = position[1];
160 position[1] = NULL;
162 else
164 while (--latest_length)
165 position[1] = position[1]->next;
167 *position_list2 = position[1]->next;
168 position[1]->next = start_position[1];
171 *lcs_ref = svn_diff__lcs(position[0], position[1],
172 subpool);
174 /* Fix up the EOF lcs element in case one of
175 * the two sequences was NULL.
177 if ((*lcs_ref)->position[0]->offset == 1)
178 (*lcs_ref)->position[0] = *position_list1;
180 if ((*lcs_ref)->position[1]->offset == 1)
181 (*lcs_ref)->position[1] = *position_list2;
183 /* Restore modified_length and latest_length */
184 modified_length = hunk->modified_length;
185 latest_length = hunk->latest_length;
187 /* Produce the resolved diff */
188 while (1)
190 if (modified_start < lcs->position[0]->offset
191 || latest_start < lcs->position[1]->offset)
193 (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
195 (*diff_ref)->type = svn_diff__type_conflict;
196 (*diff_ref)->original_start = hunk->original_start;
197 (*diff_ref)->original_length = hunk->original_length;
198 (*diff_ref)->modified_start = modified_start - 1;
199 (*diff_ref)->modified_length = lcs->position[0]->offset
200 - modified_start;
201 (*diff_ref)->latest_start = latest_start - 1;
202 (*diff_ref)->latest_length = lcs->position[1]->offset
203 - latest_start;
204 (*diff_ref)->resolved_diff = NULL;
206 diff_ref = &(*diff_ref)->next;
209 /* Detect the EOF */
210 if (lcs->length == 0)
211 break;
213 modified_start = lcs->position[0]->offset;
214 latest_start = lcs->position[1]->offset;
216 (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
218 (*diff_ref)->type = svn_diff__type_diff_common;
219 (*diff_ref)->original_start = hunk->original_start;
220 (*diff_ref)->original_length = hunk->original_length;
221 (*diff_ref)->modified_start = modified_start - 1;
222 (*diff_ref)->modified_length = lcs->length;
223 (*diff_ref)->latest_start = latest_start - 1;
224 (*diff_ref)->latest_length = lcs->length;
225 (*diff_ref)->resolved_diff = NULL;
227 diff_ref = &(*diff_ref)->next;
229 modified_start += lcs->length;
230 latest_start += lcs->length;
232 lcs = lcs->next;
235 *diff_ref = NULL;
237 svn_pool_destroy(subpool);
241 svn_error_t *
242 svn_diff_diff3(svn_diff_t **diff,
243 void *diff_baton,
244 const svn_diff_fns_t *vtable,
245 apr_pool_t *pool)
247 svn_diff__tree_t *tree;
248 svn_diff__position_t *position_list[3];
249 svn_diff__lcs_t *lcs_om;
250 svn_diff__lcs_t *lcs_ol;
251 apr_pool_t *subpool;
252 apr_pool_t *treepool;
254 *diff = NULL;
256 subpool = svn_pool_create(pool);
257 treepool = svn_pool_create(pool);
259 svn_diff__tree_create(&tree, treepool);
261 SVN_ERR(svn_diff__get_tokens(&position_list[0],
262 tree,
263 diff_baton, vtable,
264 svn_diff_datasource_original,
265 subpool));
267 SVN_ERR(svn_diff__get_tokens(&position_list[1],
268 tree,
269 diff_baton, vtable,
270 svn_diff_datasource_modified,
271 subpool));
273 SVN_ERR(svn_diff__get_tokens(&position_list[2],
274 tree,
275 diff_baton, vtable,
276 svn_diff_datasource_latest,
277 subpool));
279 /* Get rid of the tokens, we don't need them to calc the diff */
280 if (vtable->token_discard_all != NULL)
281 vtable->token_discard_all(diff_baton);
283 /* We don't need the nodes in the tree either anymore, nor the tree itself */
284 svn_pool_destroy(treepool);
286 /* Get the lcs for original-modified and original-latest */
287 lcs_om = svn_diff__lcs(position_list[0], position_list[1],
288 subpool);
289 lcs_ol = svn_diff__lcs(position_list[0], position_list[2],
290 subpool);
292 /* Produce a merged diff */
294 svn_diff_t **diff_ref = diff;
296 apr_off_t original_start = 1;
297 apr_off_t modified_start = 1;
298 apr_off_t latest_start = 1;
299 apr_off_t original_sync;
300 apr_off_t modified_sync;
301 apr_off_t latest_sync;
302 apr_off_t common_length;
303 apr_off_t original_length;
304 apr_off_t modified_length;
305 apr_off_t latest_length;
306 svn_boolean_t is_modified;
307 svn_boolean_t is_latest;
308 svn_diff__position_t sentinel_position[2];
310 /* Point the position lists to the start of the list
311 * so that common_diff/conflict detection actually is
312 * able to work.
314 if (position_list[1])
316 sentinel_position[0].next = position_list[1]->next;
317 sentinel_position[0].offset = position_list[1]->offset + 1;
318 position_list[1]->next = &sentinel_position[0];
319 position_list[1] = sentinel_position[0].next;
321 else
323 sentinel_position[0].offset = 1;
324 sentinel_position[0].next = NULL;
325 position_list[1] = &sentinel_position[0];
328 if (position_list[2])
330 sentinel_position[1].next = position_list[2]->next;
331 sentinel_position[1].offset = position_list[2]->offset + 1;
332 position_list[2]->next = &sentinel_position[1];
333 position_list[2] = sentinel_position[1].next;
335 else
337 sentinel_position[1].offset = 1;
338 sentinel_position[1].next = NULL;
339 position_list[2] = &sentinel_position[1];
342 while (1)
344 /* Find the sync points */
345 while (1)
347 if (lcs_om->position[0]->offset > lcs_ol->position[0]->offset)
349 original_sync = lcs_om->position[0]->offset;
351 while (lcs_ol->position[0]->offset + lcs_ol->length
352 < original_sync)
353 lcs_ol = lcs_ol->next;
355 /* If the sync point is the EOF, and our current lcs segment
356 * doesn't reach as far as EOF, we need to skip this segment.
358 if (lcs_om->length == 0 && lcs_ol->length > 0
359 && lcs_ol->position[0]->offset + lcs_ol->length
360 == original_sync
361 && lcs_ol->position[1]->offset + lcs_ol->length
362 != lcs_ol->next->position[1]->offset)
363 lcs_ol = lcs_ol->next;
365 if (lcs_ol->position[0]->offset <= original_sync)
366 break;
368 else
370 original_sync = lcs_ol->position[0]->offset;
372 while (lcs_om->position[0]->offset + lcs_om->length
373 < original_sync)
374 lcs_om = lcs_om->next;
376 /* If the sync point is the EOF, and our current lcs segment
377 * doesn't reach as far as EOF, we need to skip this segment.
379 if (lcs_ol->length == 0 && lcs_om->length > 0
380 && lcs_om->position[0]->offset + lcs_om->length
381 == original_sync
382 && lcs_om->position[1]->offset + lcs_om->length
383 != lcs_om->next->position[1]->offset)
384 lcs_om = lcs_om->next;
386 if (lcs_om->position[0]->offset <= original_sync)
387 break;
391 modified_sync = lcs_om->position[1]->offset
392 + (original_sync - lcs_om->position[0]->offset);
393 latest_sync = lcs_ol->position[1]->offset
394 + (original_sync - lcs_ol->position[0]->offset);
396 /* Determine what is modified, if anything */
397 is_modified = lcs_om->position[0]->offset - original_start > 0
398 || lcs_om->position[1]->offset - modified_start > 0;
400 is_latest = lcs_ol->position[0]->offset - original_start > 0
401 || lcs_ol->position[1]->offset - latest_start > 0;
403 if (is_modified || is_latest)
405 original_length = original_sync - original_start;
406 modified_length = modified_sync - modified_start;
407 latest_length = latest_sync - latest_start;
409 (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
411 (*diff_ref)->original_start = original_start - 1;
412 (*diff_ref)->original_length = original_sync - original_start;
413 (*diff_ref)->modified_start = modified_start - 1;
414 (*diff_ref)->modified_length = modified_length;
415 (*diff_ref)->latest_start = latest_start - 1;
416 (*diff_ref)->latest_length = latest_length;
417 (*diff_ref)->resolved_diff = NULL;
419 if (is_modified && is_latest)
421 svn_diff__resolve_conflict(*diff_ref,
422 &position_list[1],
423 &position_list[2],
424 pool);
426 else if (is_modified)
428 (*diff_ref)->type = svn_diff__type_diff_modified;
430 else
432 (*diff_ref)->type = svn_diff__type_diff_latest;
435 diff_ref = &(*diff_ref)->next;
438 /* Detect EOF */
439 if (lcs_om->length == 0 || lcs_ol->length == 0)
440 break;
442 modified_length = lcs_om->length
443 - (original_sync - lcs_om->position[0]->offset);
444 latest_length = lcs_ol->length
445 - (original_sync - lcs_ol->position[0]->offset);
446 common_length = modified_length < latest_length
447 ? modified_length : latest_length;
449 (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
451 (*diff_ref)->type = svn_diff__type_common;
452 (*diff_ref)->original_start = original_sync - 1;
453 (*diff_ref)->original_length = common_length;
454 (*diff_ref)->modified_start = modified_sync - 1;
455 (*diff_ref)->modified_length = common_length;
456 (*diff_ref)->latest_start = latest_sync - 1;
457 (*diff_ref)->latest_length = common_length;
458 (*diff_ref)->resolved_diff = NULL;
460 diff_ref = &(*diff_ref)->next;
462 /* Set the new offsets */
463 original_start = original_sync + common_length;
464 modified_start = modified_sync + common_length;
465 latest_start = latest_sync + common_length;
467 /* Make it easier for diff_common/conflict detection
468 by recording last lcs start positions
470 if (position_list[1]->offset < lcs_om->position[1]->offset)
471 position_list[1] = lcs_om->position[1];
473 if (position_list[2]->offset < lcs_ol->position[1]->offset)
474 position_list[2] = lcs_ol->position[1];
476 /* Make sure we are pointing to lcs entries beyond
477 * the range we just processed
479 while (original_start >= lcs_om->position[0]->offset + lcs_om->length
480 && lcs_om->length > 0)
482 lcs_om = lcs_om->next;
485 while (original_start >= lcs_ol->position[0]->offset + lcs_ol->length
486 && lcs_ol->length > 0)
488 lcs_ol = lcs_ol->next;
492 *diff_ref = NULL;
495 svn_pool_destroy(subpool);
497 return SVN_NO_ERROR;