1/* 2 * diff_memory.c : routines for doing diffs on in-memory data 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24#define WANT_MEMFUNC 25#define WANT_STRFUNC 26#include <apr.h> 27#include <apr_want.h> 28#include <apr_tables.h> 29 30#include <assert.h> 31 32#include "svn_diff.h" 33#include "svn_pools.h" 34#include "svn_types.h" 35#include "svn_string.h" 36#include "svn_utf.h" 37#include "diff.h" 38#include "svn_private_config.h" 39#include "private/svn_adler32.h" 40#include "private/svn_diff_private.h" 41 42typedef struct source_tokens_t 43{ 44 /* A token simply is an svn_string_t pointing to 45 the data of the in-memory data source, containing 46 the raw token text, with length stored in the string */ 47 apr_array_header_t *tokens; 48 49 /* Next token to be consumed */ 50 apr_size_t next_token; 51 52 /* The source, containing the in-memory data to be diffed */ 53 const svn_string_t *source; 54 55 /* The last token ends with a newline character (sequence) */ 56 svn_boolean_t ends_without_eol; 57} source_tokens_t; 58 59typedef struct diff_mem_baton_t 60{ 61 /* The tokens for each of the sources */ 62 source_tokens_t sources[4]; 63 64 /* Normalization buffer; we only ever compare 2 tokens at the same time */ 65 char *normalization_buf[2]; 66 67 /* Options for normalized comparison of the data sources */ 68 const svn_diff_file_options_t *normalization_options; 69} diff_mem_baton_t; 70 71 72static int 73datasource_to_index(svn_diff_datasource_e datasource) 74{ 75 switch (datasource) 76 { 77 case svn_diff_datasource_original: 78 return 0; 79 80 case svn_diff_datasource_modified: 81 return 1; 82 83 case svn_diff_datasource_latest: 84 return 2; 85 86 case svn_diff_datasource_ancestor: 87 return 3; 88 } 89 90 return -1; 91} 92 93 94/* Implements svn_diff_fns2_t::datasources_open */ 95static svn_error_t * 96datasources_open(void *baton, 97 apr_off_t *prefix_lines, 98 apr_off_t *suffix_lines, 99 const svn_diff_datasource_e *datasources, 100 apr_size_t datasources_len) 101{ 102 /* Do nothing: everything is already there and initialized to 0 */ 103 *prefix_lines = 0; 104 *suffix_lines = 0; 105 return SVN_NO_ERROR; 106} 107 108 109/* Implements svn_diff_fns2_t::datasource_close */ 110static svn_error_t * 111datasource_close(void *baton, svn_diff_datasource_e datasource) 112{ 113 /* Do nothing. The compare_token function needs previous datasources 114 * to stay available until all datasources are processed. 115 */ 116 117 return SVN_NO_ERROR; 118} 119 120 121/* Implements svn_diff_fns2_t::datasource_get_next_token */ 122static svn_error_t * 123datasource_get_next_token(apr_uint32_t *hash, void **token, void *baton, 124 svn_diff_datasource_e datasource) 125{ 126 diff_mem_baton_t *mem_baton = baton; 127 source_tokens_t *src = &(mem_baton->sources[datasource_to_index(datasource)]); 128 129 if ((apr_size_t)src->tokens->nelts > src->next_token) 130 { 131 /* There are actually tokens to be returned */ 132 char *buf = mem_baton->normalization_buf[0]; 133 svn_string_t *tok = (*token) 134 = APR_ARRAY_IDX(src->tokens, src->next_token, svn_string_t *); 135 apr_off_t len = tok->len; 136 svn_diff__normalize_state_t state 137 = svn_diff__normalize_state_normal; 138 139 svn_diff__normalize_buffer(&buf, &len, &state, tok->data, 140 mem_baton->normalization_options); 141 *hash = svn__adler32(0, buf, len); 142 src->next_token++; 143 } 144 else 145 *token = NULL; 146 147 return SVN_NO_ERROR; 148} 149 150/* Implements svn_diff_fns2_t::token_compare */ 151static svn_error_t * 152token_compare(void *baton, void *token1, void *token2, int *result) 153{ 154 /* Implement the same behaviour as diff_file.c:token_compare(), 155 but be simpler, because we know we'll have all data in memory */ 156 diff_mem_baton_t *btn = baton; 157 svn_string_t *t1 = token1; 158 svn_string_t *t2 = token2; 159 char *buf1 = btn->normalization_buf[0]; 160 char *buf2 = btn->normalization_buf[1]; 161 apr_off_t len1 = t1->len; 162 apr_off_t len2 = t2->len; 163 svn_diff__normalize_state_t state = svn_diff__normalize_state_normal; 164 165 svn_diff__normalize_buffer(&buf1, &len1, &state, t1->data, 166 btn->normalization_options); 167 state = svn_diff__normalize_state_normal; 168 svn_diff__normalize_buffer(&buf2, &len2, &state, t2->data, 169 btn->normalization_options); 170 171 if (len1 != len2) 172 *result = (len1 < len2) ? -1 : 1; 173 else 174 *result = (len1 == 0) ? 0 : memcmp(buf1, buf2, (size_t) len1); 175 176 return SVN_NO_ERROR; 177} 178 179/* Implements svn_diff_fns2_t::token_discard */ 180static void 181token_discard(void *baton, void *token) 182{ 183 /* No-op, we have no use for discarded tokens... */ 184} 185 186 187/* Implements svn_diff_fns2_t::token_discard_all */ 188static void 189token_discard_all(void *baton) 190{ 191 /* Do nothing. 192 Note that in the file case, this function discards all 193 tokens allocated, but we're geared toward small in-memory diffs. 194 Meaning that there's no special pool to clear. 195 */ 196} 197 198 199static const svn_diff_fns2_t svn_diff__mem_vtable = 200{ 201 datasources_open, 202 datasource_close, 203 datasource_get_next_token, 204 token_compare, 205 token_discard, 206 token_discard_all 207}; 208 209/* Fill SRC with the diff tokens (e.g. lines). 210 211 TEXT is assumed to live long enough for the tokens to 212 stay valid during their lifetime: no data is copied, 213 instead, svn_string_t's are allocated pointing straight 214 into TEXT. 215*/ 216static void 217fill_source_tokens(source_tokens_t *src, 218 const svn_string_t *text, 219 apr_pool_t *pool) 220{ 221 const char *curp; 222 const char *endp; 223 const char *startp; 224 225 src->tokens = apr_array_make(pool, 0, sizeof(svn_string_t *)); 226 src->next_token = 0; 227 src->source = text; 228 229 for (startp = curp = text->data, endp = curp + text->len; 230 curp != endp; curp++) 231 { 232 if (curp != endp && *curp == '\r' && *(curp + 1) == '\n') 233 curp++; 234 235 if (*curp == '\r' || *curp == '\n') 236 { 237 APR_ARRAY_PUSH(src->tokens, svn_string_t *) = 238 svn_string_ncreate(startp, curp - startp + 1, pool); 239 240 startp = curp + 1; 241 } 242 } 243 244 /* If there's anything remaining (ie last line doesn't have a newline) */ 245 if (startp != endp) 246 { 247 APR_ARRAY_PUSH(src->tokens, svn_string_t *) = 248 svn_string_ncreate(startp, endp - startp, pool); 249 src->ends_without_eol = TRUE; 250 } 251 else 252 src->ends_without_eol = FALSE; 253} 254 255 256static void 257alloc_normalization_bufs(diff_mem_baton_t *btn, 258 int sources, 259 apr_pool_t *pool) 260{ 261 apr_size_t max_len = 0; 262 apr_off_t idx; 263 int i; 264 265 for (i = 0; i < sources; i++) 266 { 267 apr_array_header_t *tokens = btn->sources[i].tokens; 268 if (tokens->nelts > 0) 269 for (idx = 0; idx < tokens->nelts; idx++) 270 { 271 apr_size_t token_len 272 = APR_ARRAY_IDX(tokens, idx, svn_string_t *)->len; 273 max_len = (max_len < token_len) ? token_len : max_len; 274 } 275 } 276 277 btn->normalization_buf[0] = apr_palloc(pool, max_len); 278 btn->normalization_buf[1] = apr_palloc(pool, max_len); 279} 280 281svn_error_t * 282svn_diff_mem_string_diff(svn_diff_t **diff, 283 const svn_string_t *original, 284 const svn_string_t *modified, 285 const svn_diff_file_options_t *options, 286 apr_pool_t *pool) 287{ 288 diff_mem_baton_t baton; 289 290 fill_source_tokens(&(baton.sources[0]), original, pool); 291 fill_source_tokens(&(baton.sources[1]), modified, pool); 292 alloc_normalization_bufs(&baton, 2, pool); 293 294 baton.normalization_options = options; 295 296 return svn_diff_diff_2(diff, &baton, &svn_diff__mem_vtable, pool); 297} 298 299svn_error_t * 300svn_diff_mem_string_diff3(svn_diff_t **diff, 301 const svn_string_t *original, 302 const svn_string_t *modified, 303 const svn_string_t *latest, 304 const svn_diff_file_options_t *options, 305 apr_pool_t *pool) 306{ 307 diff_mem_baton_t baton; 308 309 fill_source_tokens(&(baton.sources[0]), original, pool); 310 fill_source_tokens(&(baton.sources[1]), modified, pool); 311 fill_source_tokens(&(baton.sources[2]), latest, pool); 312 alloc_normalization_bufs(&baton, 3, pool); 313 314 baton.normalization_options = options; 315 316 return svn_diff_diff3_2(diff, &baton, &svn_diff__mem_vtable, pool); 317} 318 319 320svn_error_t * 321svn_diff_mem_string_diff4(svn_diff_t **diff, 322 const svn_string_t *original, 323 const svn_string_t *modified, 324 const svn_string_t *latest, 325 const svn_string_t *ancestor, 326 const svn_diff_file_options_t *options, 327 apr_pool_t *pool) 328{ 329 diff_mem_baton_t baton; 330 331 fill_source_tokens(&(baton.sources[0]), original, pool); 332 fill_source_tokens(&(baton.sources[1]), modified, pool); 333 fill_source_tokens(&(baton.sources[2]), latest, pool); 334 fill_source_tokens(&(baton.sources[3]), ancestor, pool); 335 alloc_normalization_bufs(&baton, 4, pool); 336 337 baton.normalization_options = options; 338 339 return svn_diff_diff4_2(diff, &baton, &svn_diff__mem_vtable, pool); 340} 341 342 343typedef enum unified_output_e 344{ 345 unified_output_context = 0, 346 unified_output_delete, 347 unified_output_insert, 348 unified_output_skip 349} unified_output_e; 350 351/* Baton for generating unified diffs */ 352typedef struct unified_output_baton_t 353{ 354 svn_stream_t *output_stream; 355 const char *header_encoding; 356 source_tokens_t sources[2]; /* 0 == original; 1 == modified */ 357 apr_off_t current_token[2]; /* current token per source */ 358 359 /* Cached markers, in header_encoding, 360 indexed using unified_output_e */ 361 const char *prefix_str[3]; 362 363 svn_stringbuf_t *hunk; /* in-progress hunk data */ 364 apr_off_t hunk_length[2]; /* 0 == original; 1 == modified */ 365 apr_off_t hunk_start[2]; /* 0 == original; 1 == modified */ 366 367 /* The delimiters of the hunk header, '@@' for text hunks and '##' for 368 * property hunks. */ 369 const char *hunk_delimiter; 370 /* The string to print after a line that does not end with a newline. 371 * It must start with a '\'. Typically "\ No newline at end of file". */ 372 const char *no_newline_string; 373 374 /* Pool for allocation of temporary memory in the callbacks 375 Should be cleared on entry of each iteration of a callback */ 376 apr_pool_t *pool; 377} output_baton_t; 378 379 380/* Append tokens (lines) FIRST up to PAST_LAST 381 from token-source index TOKENS with change-type TYPE 382 to the current hunk. 383*/ 384static svn_error_t * 385output_unified_token_range(output_baton_t *btn, 386 int tokens, 387 unified_output_e type, 388 apr_off_t until) 389{ 390 source_tokens_t *source = &btn->sources[tokens]; 391 392 if (until > source->tokens->nelts) 393 until = source->tokens->nelts; 394 395 if (until <= btn->current_token[tokens]) 396 return SVN_NO_ERROR; 397 398 /* Do the loop with prefix and token */ 399 while (TRUE) 400 { 401 svn_string_t *token = 402 APR_ARRAY_IDX(source->tokens, btn->current_token[tokens], 403 svn_string_t *); 404 405 if (type != unified_output_skip) 406 { 407 svn_stringbuf_appendcstr(btn->hunk, btn->prefix_str[type]); 408 svn_stringbuf_appendbytes(btn->hunk, token->data, token->len); 409 } 410 411 if (type == unified_output_context) 412 { 413 btn->hunk_length[0]++; 414 btn->hunk_length[1]++; 415 } 416 else if (type == unified_output_delete) 417 btn->hunk_length[0]++; 418 else if (type == unified_output_insert) 419 btn->hunk_length[1]++; 420 421 /* ### TODO: Add skip processing for -p handling? */ 422 423 btn->current_token[tokens]++; 424 if (btn->current_token[tokens] == until) 425 break; 426 } 427 428 if (btn->current_token[tokens] == source->tokens->nelts 429 && source->ends_without_eol) 430 { 431 const char *out_str; 432 433 SVN_ERR(svn_utf_cstring_from_utf8_ex2( 434 &out_str, btn->no_newline_string, 435 btn->header_encoding, btn->pool)); 436 svn_stringbuf_appendcstr(btn->hunk, out_str); 437 } 438 439 440 441 return SVN_NO_ERROR; 442} 443 444/* Flush the hunk currently built up in BATON 445 into the BATON's output_stream. 446 Use the specified HUNK_DELIMITER. 447 If HUNK_DELIMITER is NULL, fall back to the default delimiter. */ 448static svn_error_t * 449output_unified_flush_hunk(output_baton_t *baton, 450 const char *hunk_delimiter) 451{ 452 apr_off_t target_token; 453 apr_size_t hunk_len; 454 apr_off_t old_start; 455 apr_off_t new_start; 456 457 if (svn_stringbuf_isempty(baton->hunk)) 458 return SVN_NO_ERROR; 459 460 svn_pool_clear(baton->pool); 461 462 /* Write the trailing context */ 463 target_token = baton->hunk_start[0] + baton->hunk_length[0] 464 + SVN_DIFF__UNIFIED_CONTEXT_SIZE; 465 SVN_ERR(output_unified_token_range(baton, 0 /*original*/, 466 unified_output_context, 467 target_token)); 468 if (hunk_delimiter == NULL) 469 hunk_delimiter = "@@"; 470 471 old_start = baton->hunk_start[0]; 472 new_start = baton->hunk_start[1]; 473 474 /* If the file is non-empty, convert the line indexes from 475 zero based to one based */ 476 if (baton->hunk_length[0]) 477 old_start++; 478 if (baton->hunk_length[1]) 479 new_start++; 480 481 /* Write the hunk header */ 482 SVN_ERR(svn_diff__unified_write_hunk_header( 483 baton->output_stream, baton->header_encoding, hunk_delimiter, 484 old_start, baton->hunk_length[0], 485 new_start, baton->hunk_length[1], 486 NULL /* hunk_extra_context */, 487 baton->pool)); 488 489 hunk_len = baton->hunk->len; 490 SVN_ERR(svn_stream_write(baton->output_stream, 491 baton->hunk->data, &hunk_len)); 492 493 /* Prepare for the next hunk */ 494 baton->hunk_length[0] = 0; 495 baton->hunk_length[1] = 0; 496 baton->hunk_start[0] = 0; 497 baton->hunk_start[1] = 0; 498 svn_stringbuf_setempty(baton->hunk); 499 500 return SVN_NO_ERROR; 501} 502 503/* Implements svn_diff_output_fns_t::output_diff_modified */ 504static svn_error_t * 505output_unified_diff_modified(void *baton, 506 apr_off_t original_start, 507 apr_off_t original_length, 508 apr_off_t modified_start, 509 apr_off_t modified_length, 510 apr_off_t latest_start, 511 apr_off_t latest_length) 512{ 513 output_baton_t *output_baton = baton; 514 apr_off_t context_prefix_length; 515 apr_off_t prev_context_end; 516 svn_boolean_t init_hunk = FALSE; 517 518 if (original_start > SVN_DIFF__UNIFIED_CONTEXT_SIZE) 519 context_prefix_length = SVN_DIFF__UNIFIED_CONTEXT_SIZE; 520 else 521 context_prefix_length = original_start; 522 523 /* Calculate where the previous hunk will end if we would write it now 524 (including the necessary context at the end) */ 525 if (output_baton->hunk_length[0] > 0 || output_baton->hunk_length[1] > 0) 526 { 527 prev_context_end = output_baton->hunk_start[0] 528 + output_baton->hunk_length[0] 529 + SVN_DIFF__UNIFIED_CONTEXT_SIZE; 530 } 531 else 532 { 533 prev_context_end = -1; 534 535 if (output_baton->hunk_start[0] == 0 536 && (original_length > 0 || modified_length > 0)) 537 init_hunk = TRUE; 538 } 539 540 /* If the changed range is far enough from the previous range, flush the current 541 hunk. */ 542 { 543 apr_off_t new_hunk_start = (original_start - context_prefix_length); 544 545 if (output_baton->current_token[0] < new_hunk_start 546 && prev_context_end <= new_hunk_start) 547 { 548 SVN_ERR(output_unified_flush_hunk(output_baton, 549 output_baton->hunk_delimiter)); 550 init_hunk = TRUE; 551 } 552 else if (output_baton->hunk_length[0] > 0 553 || output_baton->hunk_length[1] > 0) 554 { 555 /* We extend the current hunk */ 556 557 /* Original: Output the context preceding the changed range */ 558 SVN_ERR(output_unified_token_range(output_baton, 0 /* original */, 559 unified_output_context, 560 original_start)); 561 } 562 } 563 564 /* Original: Skip lines until we are at the beginning of the context we want 565 to display */ 566 SVN_ERR(output_unified_token_range(output_baton, 0 /* original */, 567 unified_output_skip, 568 original_start - context_prefix_length)); 569 570 if (init_hunk) 571 { 572 SVN_ERR_ASSERT(output_baton->hunk_length[0] == 0 573 && output_baton->hunk_length[1] == 0); 574 575 output_baton->hunk_start[0] = original_start - context_prefix_length; 576 output_baton->hunk_start[1] = modified_start - context_prefix_length; 577 } 578 579 /* Modified: Skip lines until we are at the start of the changed range */ 580 SVN_ERR(output_unified_token_range(output_baton, 1 /* modified */, 581 unified_output_skip, 582 modified_start)); 583 584 /* Original: Output the context preceding the changed range */ 585 SVN_ERR(output_unified_token_range(output_baton, 0 /* original */, 586 unified_output_context, 587 original_start)); 588 589 /* Both: Output the changed range */ 590 SVN_ERR(output_unified_token_range(output_baton, 0 /* original */, 591 unified_output_delete, 592 original_start + original_length)); 593 SVN_ERR(output_unified_token_range(output_baton, 1 /* modified */, 594 unified_output_insert, 595 modified_start + modified_length)); 596 597 return SVN_NO_ERROR; 598} 599 600static const svn_diff_output_fns_t mem_output_unified_vtable = 601{ 602 NULL, /* output_common */ 603 output_unified_diff_modified, 604 NULL, /* output_diff_latest */ 605 NULL, /* output_diff_common */ 606 NULL /* output_conflict */ 607}; 608 609 610svn_error_t * 611svn_diff_mem_string_output_unified2(svn_stream_t *output_stream, 612 svn_diff_t *diff, 613 svn_boolean_t with_diff_header, 614 const char *hunk_delimiter, 615 const char *original_header, 616 const char *modified_header, 617 const char *header_encoding, 618 const svn_string_t *original, 619 const svn_string_t *modified, 620 apr_pool_t *pool) 621{ 622 623 if (svn_diff_contains_diffs(diff)) 624 { 625 output_baton_t baton; 626 627 memset(&baton, 0, sizeof(baton)); 628 baton.output_stream = output_stream; 629 baton.pool = svn_pool_create(pool); 630 baton.header_encoding = header_encoding; 631 baton.hunk = svn_stringbuf_create_empty(pool); 632 baton.hunk_delimiter = hunk_delimiter; 633 baton.no_newline_string 634 = (hunk_delimiter == NULL || strcmp(hunk_delimiter, "##") != 0) 635 ? APR_EOL_STR SVN_DIFF__NO_NEWLINE_AT_END_OF_FILE APR_EOL_STR 636 : APR_EOL_STR SVN_DIFF__NO_NEWLINE_AT_END_OF_PROPERTY APR_EOL_STR; 637 638 SVN_ERR(svn_utf_cstring_from_utf8_ex2 639 (&(baton.prefix_str[unified_output_context]), " ", 640 header_encoding, pool)); 641 SVN_ERR(svn_utf_cstring_from_utf8_ex2 642 (&(baton.prefix_str[unified_output_delete]), "-", 643 header_encoding, pool)); 644 SVN_ERR(svn_utf_cstring_from_utf8_ex2 645 (&(baton.prefix_str[unified_output_insert]), "+", 646 header_encoding, pool)); 647 648 fill_source_tokens(&baton.sources[0], original, pool); 649 fill_source_tokens(&baton.sources[1], modified, pool); 650 651 if (with_diff_header) 652 { 653 SVN_ERR(svn_diff__unidiff_write_header( 654 output_stream, header_encoding, 655 original_header, modified_header, pool)); 656 } 657 658 SVN_ERR(svn_diff_output(diff, &baton, 659 &mem_output_unified_vtable)); 660 661 SVN_ERR(output_unified_flush_hunk(&baton, hunk_delimiter)); 662 663 svn_pool_destroy(baton.pool); 664 } 665 666 return SVN_NO_ERROR; 667} 668 669svn_error_t * 670svn_diff_mem_string_output_unified(svn_stream_t *output_stream, 671 svn_diff_t *diff, 672 const char *original_header, 673 const char *modified_header, 674 const char *header_encoding, 675 const svn_string_t *original, 676 const svn_string_t *modified, 677 apr_pool_t *pool) 678{ 679 SVN_ERR(svn_diff_mem_string_output_unified2(output_stream, 680 diff, 681 TRUE, 682 NULL, 683 original_header, 684 modified_header, 685 header_encoding, 686 original, 687 modified, 688 pool)); 689 return SVN_NO_ERROR; 690} 691 692 693 694/* diff3 merge output */ 695 696/* A stream to remember *leading* context. Note that this stream does 697 *not* copy the data that it is remembering; it just saves 698 *pointers! */ 699typedef struct context_saver_t { 700 svn_stream_t *stream; 701 const char *data[SVN_DIFF__UNIFIED_CONTEXT_SIZE]; 702 apr_size_t len[SVN_DIFF__UNIFIED_CONTEXT_SIZE]; 703 apr_size_t next_slot; 704 apr_size_t total_written; 705} context_saver_t; 706 707 708static svn_error_t * 709context_saver_stream_write(void *baton, 710 const char *data, 711 apr_size_t *len) 712{ 713 context_saver_t *cs = baton; 714 cs->data[cs->next_slot] = data; 715 cs->len[cs->next_slot] = *len; 716 cs->next_slot = (cs->next_slot + 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE; 717 cs->total_written++; 718 return SVN_NO_ERROR; 719} 720 721 722typedef struct merge_output_baton_t 723{ 724 svn_stream_t *output_stream; 725 726 /* Tokenized source text */ 727 source_tokens_t sources[3]; 728 apr_off_t next_token[3]; 729 730 /* Markers for marking conflicted sections */ 731 const char *markers[4]; /* 0 = original, 1 = modified, 732 2 = separator, 3 = latest (end) */ 733 const char *marker_eol; 734 735 svn_diff_conflict_display_style_t conflict_style; 736 737 /* The rest of the fields are for 738 svn_diff_conflict_display_only_conflicts only. Note that for 739 these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or 740 (soon after a conflict) a "trailing context stream", never the 741 actual output stream.*/ 742 /* The actual output stream. */ 743 svn_stream_t *real_output_stream; 744 context_saver_t *context_saver; 745 /* Used to allocate context_saver and trailing context streams, and 746 for some printfs. */ 747 apr_pool_t *pool; 748} merge_output_baton_t; 749 750 751static svn_error_t * 752flush_context_saver(context_saver_t *cs, 753 svn_stream_t *output_stream) 754{ 755 int i; 756 for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++) 757 { 758 apr_size_t slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE; 759 if (cs->data[slot]) 760 { 761 apr_size_t len = cs->len[slot]; 762 SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len)); 763 } 764 } 765 return SVN_NO_ERROR; 766} 767 768 769static void 770make_context_saver(merge_output_baton_t *mob) 771{ 772 context_saver_t *cs; 773 774 svn_pool_clear(mob->pool); 775 cs = apr_pcalloc(mob->pool, sizeof(*cs)); 776 cs->stream = svn_stream_empty(mob->pool); 777 svn_stream_set_baton(cs->stream, cs); 778 svn_stream_set_write(cs->stream, context_saver_stream_write); 779 mob->context_saver = cs; 780 mob->output_stream = cs->stream; 781} 782 783 784/* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to 785 BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to 786 a context_saver; used for *trailing* context. */ 787 788struct trailing_context_printer { 789 apr_size_t lines_to_print; 790 merge_output_baton_t *mob; 791}; 792 793 794static svn_error_t * 795trailing_context_printer_write(void *baton, 796 const char *data, 797 apr_size_t *len) 798{ 799 struct trailing_context_printer *tcp = baton; 800 SVN_ERR_ASSERT(tcp->lines_to_print > 0); 801 SVN_ERR(svn_stream_write(tcp->mob->real_output_stream, data, len)); 802 tcp->lines_to_print--; 803 if (tcp->lines_to_print == 0) 804 make_context_saver(tcp->mob); 805 return SVN_NO_ERROR; 806} 807 808 809static void 810make_trailing_context_printer(merge_output_baton_t *btn) 811{ 812 struct trailing_context_printer *tcp; 813 svn_stream_t *s; 814 815 svn_pool_clear(btn->pool); 816 817 tcp = apr_pcalloc(btn->pool, sizeof(*tcp)); 818 tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE; 819 tcp->mob = btn; 820 s = svn_stream_empty(btn->pool); 821 svn_stream_set_baton(s, tcp); 822 svn_stream_set_write(s, trailing_context_printer_write); 823 btn->output_stream = s; 824} 825 826 827static svn_error_t * 828output_merge_token_range(apr_size_t *lines_printed_p, 829 merge_output_baton_t *btn, 830 int idx, apr_off_t first, 831 apr_off_t length) 832{ 833 apr_array_header_t *tokens = btn->sources[idx].tokens; 834 apr_size_t lines_printed = 0; 835 836 for (; length > 0 && first < tokens->nelts; length--, first++) 837 { 838 svn_string_t *token = APR_ARRAY_IDX(tokens, first, svn_string_t *); 839 apr_size_t len = token->len; 840 841 /* Note that the trailing context printer assumes that 842 svn_stream_write is called exactly once per line. */ 843 SVN_ERR(svn_stream_write(btn->output_stream, token->data, &len)); 844 lines_printed++; 845 } 846 847 if (lines_printed_p) 848 *lines_printed_p = lines_printed; 849 850 return SVN_NO_ERROR; 851} 852 853static svn_error_t * 854output_marker_eol(merge_output_baton_t *btn) 855{ 856 return svn_stream_puts(btn->output_stream, btn->marker_eol); 857} 858 859static svn_error_t * 860output_merge_marker(merge_output_baton_t *btn, int idx) 861{ 862 SVN_ERR(svn_stream_puts(btn->output_stream, btn->markers[idx])); 863 return output_marker_eol(btn); 864} 865 866static svn_error_t * 867output_common_modified(void *baton, 868 apr_off_t original_start, apr_off_t original_length, 869 apr_off_t modified_start, apr_off_t modified_length, 870 apr_off_t latest_start, apr_off_t latest_length) 871{ 872 return output_merge_token_range(NULL, baton, 1/*modified*/, 873 modified_start, modified_length); 874} 875 876static svn_error_t * 877output_latest(void *baton, 878 apr_off_t original_start, apr_off_t original_length, 879 apr_off_t modified_start, apr_off_t modified_length, 880 apr_off_t latest_start, apr_off_t latest_length) 881{ 882 return output_merge_token_range(NULL, baton, 2/*latest*/, 883 latest_start, latest_length); 884} 885 886static svn_error_t * 887output_conflict(void *baton, 888 apr_off_t original_start, apr_off_t original_length, 889 apr_off_t modified_start, apr_off_t modified_length, 890 apr_off_t latest_start, apr_off_t latest_length, 891 svn_diff_t *diff); 892 893static const svn_diff_output_fns_t merge_output_vtable = 894{ 895 output_common_modified, /* common */ 896 output_common_modified, /* modified */ 897 output_latest, 898 output_common_modified, /* output_diff_common */ 899 output_conflict 900}; 901 902static svn_error_t * 903output_conflict(void *baton, 904 apr_off_t original_start, apr_off_t original_length, 905 apr_off_t modified_start, apr_off_t modified_length, 906 apr_off_t latest_start, apr_off_t latest_length, 907 svn_diff_t *diff) 908{ 909 merge_output_baton_t *btn = baton; 910 911 svn_diff_conflict_display_style_t style = btn->conflict_style; 912 913 if (style == svn_diff_conflict_display_resolved_modified_latest) 914 { 915 if (diff) 916 return svn_diff_output(diff, baton, &merge_output_vtable); 917 else 918 style = svn_diff_conflict_display_modified_latest; 919 } 920 921 if (style == svn_diff_conflict_display_modified_latest || 922 style == svn_diff_conflict_display_modified_original_latest) 923 { 924 SVN_ERR(output_merge_marker(btn, 1/*modified*/)); 925 SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/, 926 modified_start, modified_length)); 927 928 if (style == svn_diff_conflict_display_modified_original_latest) 929 { 930 SVN_ERR(output_merge_marker(btn, 0/*original*/)); 931 SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/, 932 original_start, original_length)); 933 } 934 935 SVN_ERR(output_merge_marker(btn, 2/*separator*/)); 936 SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/, 937 latest_start, latest_length)); 938 SVN_ERR(output_merge_marker(btn, 3/*latest (end)*/)); 939 } 940 else if (style == svn_diff_conflict_display_modified) 941 SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/, 942 modified_start, modified_length)); 943 else if (style == svn_diff_conflict_display_latest) 944 SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/, 945 latest_start, latest_length)); 946 else /* unknown style */ 947 SVN_ERR_MALFUNCTION(); 948 949 return SVN_NO_ERROR; 950} 951 952 953static svn_error_t * 954output_conflict_with_context(void *baton, 955 apr_off_t original_start, 956 apr_off_t original_length, 957 apr_off_t modified_start, 958 apr_off_t modified_length, 959 apr_off_t latest_start, 960 apr_off_t latest_length, 961 svn_diff_t *diff) 962{ 963 merge_output_baton_t *btn = baton; 964 965 /* Are we currently saving starting context (as opposed to printing 966 trailing context)? If so, flush it. */ 967 if (btn->output_stream == btn->context_saver->stream) 968 { 969 if (btn->context_saver->total_written > SVN_DIFF__UNIFIED_CONTEXT_SIZE) 970 SVN_ERR(svn_stream_puts(btn->real_output_stream, "@@\n")); 971 SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream)); 972 } 973 974 /* Print to the real output stream. */ 975 btn->output_stream = btn->real_output_stream; 976 977 /* Output the conflict itself. */ 978 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool, 979 (modified_length == 1 980 ? "%s (%" APR_OFF_T_FMT ")" 981 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"), 982 btn->markers[1], 983 modified_start + 1, modified_length)); 984 SVN_ERR(output_marker_eol(btn)); 985 SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/, 986 modified_start, modified_length)); 987 988 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool, 989 (original_length == 1 990 ? "%s (%" APR_OFF_T_FMT ")" 991 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"), 992 btn->markers[0], 993 original_start + 1, original_length)); 994 SVN_ERR(output_marker_eol(btn)); 995 SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/, 996 original_start, original_length)); 997 998 SVN_ERR(output_merge_marker(btn, 2/*separator*/)); 999 SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/, 1000 latest_start, latest_length)); 1001 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool, 1002 (latest_length == 1 1003 ? "%s (%" APR_OFF_T_FMT ")" 1004 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"), 1005 btn->markers[3], 1006 latest_start + 1, latest_length)); 1007 SVN_ERR(output_marker_eol(btn)); 1008 1009 /* Go into print-trailing-context mode instead. */ 1010 make_trailing_context_printer(btn); 1011 1012 return SVN_NO_ERROR; 1013} 1014 1015 1016static const svn_diff_output_fns_t merge_only_conflicts_output_vtable = 1017{ 1018 output_common_modified, 1019 output_common_modified, 1020 output_latest, 1021 output_common_modified, 1022 output_conflict_with_context 1023}; 1024 1025 1026/* TOKEN is the first token in the modified file. 1027 Return its line-ending, if any. */ 1028static const char * 1029detect_eol(svn_string_t *token) 1030{ 1031 const char *curp; 1032 1033 if (token->len == 0) 1034 return NULL; 1035 1036 curp = token->data + token->len - 1; 1037 if (*curp == '\r') 1038 return "\r"; 1039 else if (*curp != '\n') 1040 return NULL; 1041 else 1042 { 1043 if (token->len == 1 1044 || *(--curp) != '\r') 1045 return "\n"; 1046 else 1047 return "\r\n"; 1048 } 1049} 1050 1051svn_error_t * 1052svn_diff_mem_string_output_merge2(svn_stream_t *output_stream, 1053 svn_diff_t *diff, 1054 const svn_string_t *original, 1055 const svn_string_t *modified, 1056 const svn_string_t *latest, 1057 const char *conflict_original, 1058 const char *conflict_modified, 1059 const char *conflict_latest, 1060 const char *conflict_separator, 1061 svn_diff_conflict_display_style_t style, 1062 apr_pool_t *pool) 1063{ 1064 merge_output_baton_t btn; 1065 const char *eol; 1066 svn_boolean_t conflicts_only = 1067 (style == svn_diff_conflict_display_only_conflicts); 1068 const svn_diff_output_fns_t *vtable = conflicts_only 1069 ? &merge_only_conflicts_output_vtable : &merge_output_vtable; 1070 1071 memset(&btn, 0, sizeof(btn)); 1072 1073 if (conflicts_only) 1074 { 1075 btn.pool = svn_pool_create(pool); 1076 make_context_saver(&btn); 1077 btn.real_output_stream = output_stream; 1078 } 1079 else 1080 btn.output_stream = output_stream; 1081 1082 fill_source_tokens(&(btn.sources[0]), original, pool); 1083 fill_source_tokens(&(btn.sources[1]), modified, pool); 1084 fill_source_tokens(&(btn.sources[2]), latest, pool); 1085 1086 btn.conflict_style = style; 1087 1088 if (btn.sources[1].tokens->nelts > 0) 1089 { 1090 eol = detect_eol(APR_ARRAY_IDX(btn.sources[1].tokens, 0, svn_string_t *)); 1091 if (!eol) 1092 eol = APR_EOL_STR; /* use the platform default */ 1093 } 1094 else 1095 eol = APR_EOL_STR; /* use the platform default */ 1096 1097 btn.marker_eol = eol; 1098 1099 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[1], 1100 conflict_modified 1101 ? conflict_modified 1102 : "<<<<<<< (modified)", 1103 pool)); 1104 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[0], 1105 conflict_original 1106 ? conflict_original 1107 : "||||||| (original)", 1108 pool)); 1109 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[2], 1110 conflict_separator 1111 ? conflict_separator 1112 : "=======", 1113 pool)); 1114 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[3], 1115 conflict_latest 1116 ? conflict_latest 1117 : ">>>>>>> (latest)", 1118 pool)); 1119 1120 SVN_ERR(svn_diff_output(diff, &btn, vtable)); 1121 if (conflicts_only) 1122 svn_pool_destroy(btn.pool); 1123 1124 return SVN_NO_ERROR; 1125} 1126 1127svn_error_t * 1128svn_diff_mem_string_output_merge(svn_stream_t *output_stream, 1129 svn_diff_t *diff, 1130 const svn_string_t *original, 1131 const svn_string_t *modified, 1132 const svn_string_t *latest, 1133 const char *conflict_original, 1134 const char *conflict_modified, 1135 const char *conflict_latest, 1136 const char *conflict_separator, 1137 svn_boolean_t display_original_in_conflict, 1138 svn_boolean_t display_resolved_conflicts, 1139 apr_pool_t *pool) 1140{ 1141 svn_diff_conflict_display_style_t style = 1142 svn_diff_conflict_display_modified_latest; 1143 1144 if (display_resolved_conflicts) 1145 style = svn_diff_conflict_display_resolved_modified_latest; 1146 1147 if (display_original_in_conflict) 1148 style = svn_diff_conflict_display_modified_original_latest; 1149 1150 return svn_diff_mem_string_output_merge2(output_stream, 1151 diff, 1152 original, 1153 modified, 1154 latest, 1155 conflict_original, 1156 conflict_modified, 1157 conflict_latest, 1158 conflict_separator, 1159 style, 1160 pool); 1161} 1162