2 * diff3.c : routines for doing diffs
\r
4 * ====================================================================
\r
5 * Copyright (c) 2000-2004 CollabNet. All rights reserved.
\r
7 * This software is licensed as described in the file COPYING, which
\r
8 * you should have received as part of this distribution. The terms
\r
9 * are also available at http://subversion.tigris.org/license-1.html.
\r
10 * If newer versions of this license are posted there, you may use a
\r
11 * newer version instead, at your option.
\r
13 * This software consists of voluntary contributions made by many
\r
14 * individuals. For exact contribution history, see the revision
\r
15 * history and logs, available at http://subversion.tigris.org/.
\r
16 * ====================================================================
\r
21 //#include <apr_pools.h>
\r
22 #include <apr_general.h>
\r
23 #include "svn_error.h"
\r
24 #include "svn_version.h"
\r
27 #include "svn_pools.h"
\r
28 #include "svn_error.h"
\r
29 #include "svn_diff.h"
\r
30 #include "svn_types.h"
\r
36 svn_diff__resolve_conflict(svn_diff_t *hunk,
\r
37 svn_diff__position_t **position_list1,
\r
38 svn_diff__position_t **position_list2,
\r
41 apr_off_t modified_start = hunk->modified_start + 1;
\r
42 apr_off_t latest_start = hunk->latest_start + 1;
\r
43 apr_off_t common_length;
\r
44 apr_off_t modified_length = hunk->modified_length;
\r
45 apr_off_t latest_length = hunk->latest_length;
\r
46 svn_diff__position_t *start_position[2];
\r
47 svn_diff__position_t *position[2];
\r
48 svn_diff__lcs_t *lcs = NULL;
\r
49 svn_diff__lcs_t **lcs_ref = &lcs;
\r
50 svn_diff_t **diff_ref = &hunk->resolved_diff;
\r
51 apr_pool_t *subpool;
\r
53 /* First find the starting positions for the
\r
57 start_position[0] = *position_list1;
\r
58 start_position[1] = *position_list2;
\r
60 while (start_position[0]->offset < modified_start)
\r
61 start_position[0] = start_position[0]->next;
\r
63 while (start_position[1]->offset < latest_start)
\r
64 start_position[1] = start_position[1]->next;
\r
66 position[0] = start_position[0];
\r
67 position[1] = start_position[1];
\r
69 common_length = modified_length < latest_length
\r
70 ? modified_length : latest_length;
\r
72 while (common_length > 0
\r
73 && position[0]->node == position[1]->node)
\r
75 position[0] = position[0]->next;
\r
76 position[1] = position[1]->next;
\r
81 if (common_length == 0
\r
82 && modified_length == latest_length)
\r
84 hunk->type = svn_diff__type_diff_common;
\r
85 hunk->resolved_diff = NULL;
\r
87 *position_list1 = position[0];
\r
88 *position_list2 = position[1];
\r
93 hunk->type = svn_diff__type_conflict;
\r
95 /* ### If we have a conflict we can try to find the
\r
96 * ### common parts in it by getting an lcs between
\r
97 * ### modified (start to start + length) and
\r
98 * ### latest (start to start + length).
\r
99 * ### We use this lcs to create a simple diff. Only
\r
100 * ### where there is a diff between the two, we have
\r
102 * ### This raises a problem; several common diffs and
\r
103 * ### conflicts can occur within the same original
\r
104 * ### block. This needs some thought.
\r
106 * ### NB: We can use the node _pointers_ to identify
\r
107 * ### different tokens
\r
110 subpool = svn_pool_create(pool);
\r
112 /* Calculate how much of the two sequences was
\r
113 * actually the same.
\r
115 common_length = (modified_length < latest_length
\r
116 ? modified_length : latest_length)
\r
119 /* If there were matching symbols at the start of
\r
120 * both sequences, record that fact.
\r
122 if (common_length > 0)
\r
124 lcs = apr_palloc(subpool, sizeof(*lcs));
\r
126 lcs->position[0] = start_position[0];
\r
127 lcs->position[1] = start_position[1];
\r
128 lcs->length = common_length;
\r
130 lcs_ref = &lcs->next;
\r
133 modified_length -= common_length;
\r
134 latest_length -= common_length;
\r
136 modified_start = start_position[0]->offset;
\r
137 latest_start = start_position[1]->offset;
\r
139 start_position[0] = position[0];
\r
140 start_position[1] = position[1];
\r
142 /* Create a new ring for svn_diff__lcs to grok.
\r
143 * We can safely do this given we don't need the
\r
144 * positions we processed anymore.
\r
146 if (modified_length == 0)
\r
148 *position_list1 = position[0];
\r
149 position[0] = NULL;
\r
153 while (--modified_length)
\r
154 position[0] = position[0]->next;
\r
156 *position_list1 = position[0]->next;
\r
157 position[0]->next = start_position[0];
\r
160 if (latest_length == 0)
\r
162 *position_list2 = position[1];
\r
163 position[1] = NULL;
\r
167 while (--latest_length)
\r
168 position[1] = position[1]->next;
\r
170 *position_list2 = position[1]->next;
\r
171 position[1]->next = start_position[1];
\r
174 *lcs_ref = svn_diff__lcs(position[0], position[1],
\r
177 /* Fix up the EOF lcs element in case one of
\r
178 * the two sequences was NULL.
\r
180 if ((*lcs_ref)->position[0]->offset == 1)
\r
181 (*lcs_ref)->position[0] = *position_list1;
\r
183 if ((*lcs_ref)->position[1]->offset == 1)
\r
184 (*lcs_ref)->position[1] = *position_list2;
\r
186 /* Restore modified_length and latest_length */
\r
187 modified_length = hunk->modified_length;
\r
188 latest_length = hunk->latest_length;
\r
190 /* Produce the resolved diff */
\r
193 if (modified_start < lcs->position[0]->offset
\r
194 || latest_start < lcs->position[1]->offset)
\r
196 (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
\r
198 (*diff_ref)->type = svn_diff__type_conflict;
\r
199 (*diff_ref)->original_start = hunk->original_start;
\r
200 (*diff_ref)->original_length = hunk->original_length;
\r
201 (*diff_ref)->modified_start = modified_start - 1;
\r
202 (*diff_ref)->modified_length = lcs->position[0]->offset
\r
204 (*diff_ref)->latest_start = latest_start - 1;
\r
205 (*diff_ref)->latest_length = lcs->position[1]->offset
\r
207 (*diff_ref)->resolved_diff = NULL;
\r
209 diff_ref = &(*diff_ref)->next;
\r
212 /* Detect the EOF */
\r
213 if (lcs->length == 0)
\r
216 modified_start = lcs->position[0]->offset;
\r
217 latest_start = lcs->position[1]->offset;
\r
219 (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
\r
221 (*diff_ref)->type = svn_diff__type_diff_common;
\r
222 (*diff_ref)->original_start = hunk->original_start;
\r
223 (*diff_ref)->original_length = hunk->original_length;
\r
224 (*diff_ref)->modified_start = modified_start - 1;
\r
225 (*diff_ref)->modified_length = lcs->length;
\r
226 (*diff_ref)->latest_start = latest_start - 1;
\r
227 (*diff_ref)->latest_length = lcs->length;
\r
228 (*diff_ref)->resolved_diff = NULL;
\r
230 diff_ref = &(*diff_ref)->next;
\r
232 modified_start += lcs->length;
\r
233 latest_start += lcs->length;
\r
240 svn_pool_destroy(subpool);
\r
245 svn_diff_diff3(svn_diff_t **diff,
\r
247 const svn_diff_fns_t *vtable,
\r
250 svn_diff__tree_t *tree;
\r
251 svn_diff__position_t *position_list[3];
\r
252 svn_diff__lcs_t *lcs_om;
\r
253 svn_diff__lcs_t *lcs_ol;
\r
254 apr_pool_t *subpool;
\r
255 apr_pool_t *treepool;
\r
259 subpool = svn_pool_create(pool);
\r
260 treepool = svn_pool_create(pool);
\r
262 svn_diff__tree_create(&tree, treepool);
\r
264 SVN_ERR(svn_diff__get_tokens(&position_list[0],
\r
266 diff_baton, vtable,
\r
267 svn_diff_datasource_original,
\r
270 SVN_ERR(svn_diff__get_tokens(&position_list[1],
\r
272 diff_baton, vtable,
\r
273 svn_diff_datasource_modified,
\r
276 SVN_ERR(svn_diff__get_tokens(&position_list[2],
\r
278 diff_baton, vtable,
\r
279 svn_diff_datasource_latest,
\r
282 /* Get rid of the tokens, we don't need them to calc the diff */
\r
283 if (vtable->token_discard_all != NULL)
\r
284 vtable->token_discard_all(diff_baton);
\r
286 /* We don't need the nodes in the tree either anymore, nor the tree itself */
\r
287 svn_pool_destroy(treepool);
\r
289 /* Get the lcs for original-modified and original-latest */
\r
290 lcs_om = svn_diff__lcs(position_list[0], position_list[1],
\r
292 lcs_ol = svn_diff__lcs(position_list[0], position_list[2],
\r
295 /* Produce a merged diff */
\r
297 svn_diff_t **diff_ref = diff;
\r
299 apr_off_t original_start = 1;
\r
300 apr_off_t modified_start = 1;
\r
301 apr_off_t latest_start = 1;
\r
302 apr_off_t original_sync;
\r
303 apr_off_t modified_sync;
\r
304 apr_off_t latest_sync;
\r
305 apr_off_t common_length;
\r
306 apr_off_t original_length;
\r
307 apr_off_t modified_length;
\r
308 apr_off_t latest_length;
\r
309 svn_boolean_t is_modified;
\r
310 svn_boolean_t is_latest;
\r
311 svn_diff__position_t sentinel_position[2];
\r
313 /* Point the position lists to the start of the list
\r
314 * so that common_diff/conflict detection actually is
\r
317 if (position_list[1])
\r
319 sentinel_position[0].next = position_list[1]->next;
\r
320 sentinel_position[0].offset = position_list[1]->offset + 1;
\r
321 position_list[1]->next = &sentinel_position[0];
\r
322 position_list[1] = sentinel_position[0].next;
\r
326 sentinel_position[0].offset = 1;
\r
327 sentinel_position[0].next = NULL;
\r
328 position_list[1] = &sentinel_position[0];
\r
331 if (position_list[2])
\r
333 sentinel_position[1].next = position_list[2]->next;
\r
334 sentinel_position[1].offset = position_list[2]->offset + 1;
\r
335 position_list[2]->next = &sentinel_position[1];
\r
336 position_list[2] = sentinel_position[1].next;
\r
340 sentinel_position[1].offset = 1;
\r
341 sentinel_position[1].next = NULL;
\r
342 position_list[2] = &sentinel_position[1];
\r
347 /* Find the sync points */
\r
350 if (lcs_om->position[0]->offset > lcs_ol->position[0]->offset)
\r
352 original_sync = lcs_om->position[0]->offset;
\r
354 while (lcs_ol->position[0]->offset + lcs_ol->length
\r
356 lcs_ol = lcs_ol->next;
\r
358 /* If the sync point is the EOF, and our current lcs segment
\r
359 * doesn't reach as far as EOF, we need to skip this segment.
\r
361 if (lcs_om->length == 0 && lcs_ol->length > 0
\r
362 && lcs_ol->position[0]->offset + lcs_ol->length
\r
364 && lcs_ol->position[1]->offset + lcs_ol->length
\r
365 != lcs_ol->next->position[1]->offset)
\r
366 lcs_ol = lcs_ol->next;
\r
368 if (lcs_ol->position[0]->offset <= original_sync)
\r
373 original_sync = lcs_ol->position[0]->offset;
\r
375 while (lcs_om->position[0]->offset + lcs_om->length
\r
377 lcs_om = lcs_om->next;
\r
379 /* If the sync point is the EOF, and our current lcs segment
\r
380 * doesn't reach as far as EOF, we need to skip this segment.
\r
382 if (lcs_ol->length == 0 && lcs_om->length > 0
\r
383 && lcs_om->position[0]->offset + lcs_om->length
\r
385 && lcs_om->position[1]->offset + lcs_om->length
\r
386 != lcs_om->next->position[1]->offset)
\r
387 lcs_om = lcs_om->next;
\r
389 if (lcs_om->position[0]->offset <= original_sync)
\r
394 modified_sync = lcs_om->position[1]->offset
\r
395 + (original_sync - lcs_om->position[0]->offset);
\r
396 latest_sync = lcs_ol->position[1]->offset
\r
397 + (original_sync - lcs_ol->position[0]->offset);
\r
399 /* Determine what is modified, if anything */
\r
400 is_modified = lcs_om->position[0]->offset - original_start > 0
\r
401 || lcs_om->position[1]->offset - modified_start > 0;
\r
403 is_latest = lcs_ol->position[0]->offset - original_start > 0
\r
404 || lcs_ol->position[1]->offset - latest_start > 0;
\r
406 if (is_modified || is_latest)
\r
408 original_length = original_sync - original_start;
\r
409 modified_length = modified_sync - modified_start;
\r
410 latest_length = latest_sync - latest_start;
\r
412 (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
\r
414 (*diff_ref)->original_start = original_start - 1;
\r
415 (*diff_ref)->original_length = original_sync - original_start;
\r
416 (*diff_ref)->modified_start = modified_start - 1;
\r
417 (*diff_ref)->modified_length = modified_length;
\r
418 (*diff_ref)->latest_start = latest_start - 1;
\r
419 (*diff_ref)->latest_length = latest_length;
\r
420 (*diff_ref)->resolved_diff = NULL;
\r
422 if (is_modified && is_latest)
\r
424 svn_diff__resolve_conflict(*diff_ref,
\r
429 else if (is_modified)
\r
431 (*diff_ref)->type = svn_diff__type_diff_modified;
\r
435 (*diff_ref)->type = svn_diff__type_diff_latest;
\r
438 diff_ref = &(*diff_ref)->next;
\r
442 if (lcs_om->length == 0 || lcs_ol->length == 0)
\r
445 modified_length = lcs_om->length
\r
446 - (original_sync - lcs_om->position[0]->offset);
\r
447 latest_length = lcs_ol->length
\r
448 - (original_sync - lcs_ol->position[0]->offset);
\r
449 common_length = modified_length < latest_length
\r
450 ? modified_length : latest_length;
\r
452 (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
\r
454 (*diff_ref)->type = svn_diff__type_common;
\r
455 (*diff_ref)->original_start = original_sync - 1;
\r
456 (*diff_ref)->original_length = common_length;
\r
457 (*diff_ref)->modified_start = modified_sync - 1;
\r
458 (*diff_ref)->modified_length = common_length;
\r
459 (*diff_ref)->latest_start = latest_sync - 1;
\r
460 (*diff_ref)->latest_length = common_length;
\r
461 (*diff_ref)->resolved_diff = NULL;
\r
463 diff_ref = &(*diff_ref)->next;
\r
465 /* Set the new offsets */
\r
466 original_start = original_sync + common_length;
\r
467 modified_start = modified_sync + common_length;
\r
468 latest_start = latest_sync + common_length;
\r
470 /* Make it easier for diff_common/conflict detection
\r
471 by recording last lcs start positions
\r
473 if (position_list[1]->offset < lcs_om->position[1]->offset)
\r
474 position_list[1] = lcs_om->position[1];
\r
476 if (position_list[2]->offset < lcs_ol->position[1]->offset)
\r
477 position_list[2] = lcs_ol->position[1];
\r
479 /* Make sure we are pointing to lcs entries beyond
\r
480 * the range we just processed
\r
482 while (original_start >= lcs_om->position[0]->offset + lcs_om->length
\r
483 && lcs_om->length > 0)
\r
485 lcs_om = lcs_om->next;
\r
488 while (original_start >= lcs_ol->position[0]->offset + lcs_ol->length
\r
489 && lcs_ol->length > 0)
\r
491 lcs_ol = lcs_ol->next;
\r
498 svn_pool_destroy(subpool);
\r
500 return SVN_NO_ERROR;
\r