file-merge.c revision 253734
1/* 2 * file-merge.c: internal file merge tool 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/* This is an interactive file merge tool with an interface similar to 25 * the interactive mode of the UNIX sdiff ("side-by-side diff") utility. 26 * The merge tool is driven by Subversion's diff code and user input. */ 27 28#include "svn_cmdline.h" 29#include "svn_dirent_uri.h" 30#include "svn_error.h" 31#include "svn_pools.h" 32#include "svn_io.h" 33#include "svn_utf.h" 34#include "svn_xml.h" 35 36#include "cl.h" 37 38#include "svn_private_config.h" 39#include "private/svn_utf_private.h" 40#include "private/svn_cmdline_private.h" 41#include "private/svn_dep_compat.h" 42 43#if APR_HAVE_SYS_IOCTL_H 44#include <sys/ioctl.h> 45#endif 46 47#if APR_HAVE_UNISTD_H 48#include <unistd.h> 49#endif 50 51#include <fcntl.h> 52#include <stdlib.h> 53 54/* Baton for functions in this file which implement svn_diff_output_fns_t. */ 55struct file_merge_baton { 56 /* The files being merged. */ 57 apr_file_t *original_file; 58 apr_file_t *modified_file; 59 apr_file_t *latest_file; 60 61 /* Counters to keep track of the current line in each file. */ 62 svn_linenum_t current_line_original; 63 svn_linenum_t current_line_modified; 64 svn_linenum_t current_line_latest; 65 66 /* The merge result is written to this file. */ 67 apr_file_t *merged_file; 68 69 /* Whether the merged file remains in conflict after the merge. */ 70 svn_boolean_t remains_in_conflict; 71 72 /* External editor command for editing chunks. */ 73 const char *editor_cmd; 74 75 /* The client configuration hash. */ 76 apr_hash_t *config; 77 78 /* Wether the merge should be aborted. */ 79 svn_boolean_t abort_merge; 80 81 /* Pool for temporary allocations. */ 82 apr_pool_t *scratch_pool; 83} file_merge_baton; 84 85/* Copy LEN lines from SOURCE_FILE to the MERGED_FILE, starting at 86 * line START. The CURRENT_LINE is the current line in the source file. 87 * The new current line is returned in *NEW_CURRENT_LINE. */ 88static svn_error_t * 89copy_to_merged_file(svn_linenum_t *new_current_line, 90 apr_file_t *merged_file, 91 apr_file_t *source_file, 92 apr_off_t start, 93 apr_off_t len, 94 svn_linenum_t current_line, 95 apr_pool_t *scratch_pool) 96{ 97 apr_pool_t *iterpool; 98 svn_stringbuf_t *line; 99 apr_size_t lines_read; 100 apr_size_t lines_copied; 101 svn_boolean_t eof; 102 svn_linenum_t orig_current_line = current_line; 103 104 lines_read = 0; 105 iterpool = svn_pool_create(scratch_pool); 106 while (current_line < start) 107 { 108 svn_pool_clear(iterpool); 109 110 SVN_ERR(svn_io_file_readline(source_file, &line, NULL, &eof, 111 APR_SIZE_MAX, iterpool, iterpool)); 112 if (eof) 113 break; 114 115 current_line++; 116 lines_read++; 117 } 118 119 lines_copied = 0; 120 while (lines_copied < len) 121 { 122 apr_size_t bytes_written; 123 const char *eol_str; 124 125 svn_pool_clear(iterpool); 126 127 SVN_ERR(svn_io_file_readline(source_file, &line, &eol_str, &eof, 128 APR_SIZE_MAX, iterpool, iterpool)); 129 if (eol_str) 130 svn_stringbuf_appendcstr(line, eol_str); 131 SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len, 132 &bytes_written, iterpool)); 133 if (bytes_written != line->len) 134 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, 135 _("Could not write data to merged file")); 136 if (eof) 137 break; 138 lines_copied++; 139 } 140 svn_pool_destroy(iterpool); 141 142 *new_current_line = orig_current_line + lines_read + lines_copied; 143 144 return SVN_NO_ERROR; 145} 146 147/* Copy common data to the merged file. */ 148static svn_error_t * 149file_merge_output_common(void *output_baton, 150 apr_off_t original_start, 151 apr_off_t original_length, 152 apr_off_t modified_start, 153 apr_off_t modified_length, 154 apr_off_t latest_start, 155 apr_off_t latest_length) 156{ 157 struct file_merge_baton *b = output_baton; 158 159 if (b->abort_merge) 160 return SVN_NO_ERROR; 161 162 SVN_ERR(copy_to_merged_file(&b->current_line_original, 163 b->merged_file, 164 b->original_file, 165 original_start, 166 original_length, 167 b->current_line_original, 168 b->scratch_pool)); 169 return SVN_NO_ERROR; 170} 171 172/* Original/latest match up, but modified differs. 173 * Copy modified data to the merged file. */ 174static svn_error_t * 175file_merge_output_diff_modified(void *output_baton, 176 apr_off_t original_start, 177 apr_off_t original_length, 178 apr_off_t modified_start, 179 apr_off_t modified_length, 180 apr_off_t latest_start, 181 apr_off_t latest_length) 182{ 183 struct file_merge_baton *b = output_baton; 184 185 if (b->abort_merge) 186 return SVN_NO_ERROR; 187 188 SVN_ERR(copy_to_merged_file(&b->current_line_modified, 189 b->merged_file, 190 b->modified_file, 191 modified_start, 192 modified_length, 193 b->current_line_modified, 194 b->scratch_pool)); 195 196 return SVN_NO_ERROR; 197} 198 199/* Original/modified match up, but latest differs. 200 * Copy latest data to the merged file. */ 201static svn_error_t * 202file_merge_output_diff_latest(void *output_baton, 203 apr_off_t original_start, 204 apr_off_t original_length, 205 apr_off_t modified_start, 206 apr_off_t modified_length, 207 apr_off_t latest_start, 208 apr_off_t latest_length) 209{ 210 struct file_merge_baton *b = output_baton; 211 212 if (b->abort_merge) 213 return SVN_NO_ERROR; 214 215 SVN_ERR(copy_to_merged_file(&b->current_line_latest, 216 b->merged_file, 217 b->latest_file, 218 latest_start, 219 latest_length, 220 b->current_line_latest, 221 b->scratch_pool)); 222 223 return SVN_NO_ERROR; 224} 225 226/* Modified/latest match up, but original differs. 227 * Copy latest data to the merged file. */ 228static svn_error_t * 229file_merge_output_diff_common(void *output_baton, 230 apr_off_t original_start, 231 apr_off_t original_length, 232 apr_off_t modified_start, 233 apr_off_t modified_length, 234 apr_off_t latest_start, 235 apr_off_t latest_length) 236{ 237 struct file_merge_baton *b = output_baton; 238 239 if (b->abort_merge) 240 return SVN_NO_ERROR; 241 242 SVN_ERR(copy_to_merged_file(&b->current_line_latest, 243 b->merged_file, 244 b->latest_file, 245 latest_start, 246 latest_length, 247 b->current_line_latest, 248 b->scratch_pool)); 249 return SVN_NO_ERROR; 250} 251 252 253/* Return LEN lines within the diff chunk staring at line START 254 * in a *LINES array of svn_stringbuf_t* elements. 255 * Store the resulting current in in *NEW_CURRENT_LINE. */ 256static svn_error_t * 257read_diff_chunk(apr_array_header_t **lines, 258 svn_linenum_t *new_current_line, 259 apr_file_t *file, 260 svn_linenum_t current_line, 261 apr_off_t start, 262 apr_off_t len, 263 apr_pool_t *result_pool, 264 apr_pool_t *scratch_pool) 265{ 266 svn_stringbuf_t *line; 267 const char *eol_str; 268 svn_boolean_t eof; 269 apr_pool_t *iterpool; 270 271 *lines = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *)); 272 273 /* Skip lines before start of range. */ 274 iterpool = svn_pool_create(scratch_pool); 275 while (current_line < start) 276 { 277 svn_pool_clear(iterpool); 278 SVN_ERR(svn_io_file_readline(file, &line, NULL, &eof, APR_SIZE_MAX, 279 iterpool, iterpool)); 280 if (eof) 281 return SVN_NO_ERROR; 282 current_line++; 283 } 284 svn_pool_destroy(iterpool); 285 286 /* Now read the lines. */ 287 do 288 { 289 SVN_ERR(svn_io_file_readline(file, &line, &eol_str, &eof, APR_SIZE_MAX, 290 result_pool, scratch_pool)); 291 if (eol_str) 292 svn_stringbuf_appendcstr(line, eol_str); 293 APR_ARRAY_PUSH(*lines, svn_stringbuf_t *) = line; 294 if (eof) 295 break; 296 current_line++; 297 } 298 while ((*lines)->nelts < len); 299 300 *new_current_line = current_line; 301 302 return SVN_NO_ERROR; 303} 304 305/* Return the terminal width in number of columns. */ 306static int 307get_term_width(void) 308{ 309 char *columns_env; 310#ifdef TIOCGWINSZ 311 int fd; 312 313 fd = open("/dev/tty", O_RDONLY, 0); 314 if (fd != -1) 315 { 316 struct winsize ws; 317 int error; 318 319 error = ioctl(fd, TIOCGWINSZ, &ws); 320 close(fd); 321 if (error != -1) 322 { 323 if (ws.ws_col < 80) 324 return 80; 325 return ws.ws_col; 326 } 327 } 328#elif defined WIN32 329 CONSOLE_SCREEN_BUFFER_INFO csbi; 330 331 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) 332 { 333 if (csbi.dwSize.X < 80) 334 return 80; 335 return csbi.dwSize.X; 336 } 337#endif 338 339 columns_env = getenv("COLUMNS"); 340 if (columns_env) 341 { 342 svn_error_t *err; 343 int cols; 344 345 err = svn_cstring_atoi(&cols, columns_env); 346 if (err) 347 { 348 svn_error_clear(err); 349 return 80; 350 } 351 352 if (cols < 80) 353 return 80; 354 return cols; 355 } 356 else 357 return 80; 358} 359 360#define LINE_DISPLAY_WIDTH ((get_term_width() / 2) - 2) 361 362/* Prepare LINE for display, pruning or extending it to an appropriate 363 * display width, and stripping the EOL marker, if any. 364 * This function assumes that the data in LINE is encoded in UTF-8. */ 365static const char * 366prepare_line_for_display(const char *line, apr_pool_t *pool) 367{ 368 svn_stringbuf_t *buf = svn_stringbuf_create(line, pool); 369 size_t width; 370 size_t line_width = LINE_DISPLAY_WIDTH; 371 apr_pool_t *iterpool; 372 373 /* Trim EOL. */ 374 if (buf->len >= 2 && 375 buf->data[buf->len - 2] == '\r' && 376 buf->data[buf->len - 1] == '\n') 377 svn_stringbuf_chop(buf, 2); 378 else if (buf->len >= 1 && 379 (buf->data[buf->len - 1] == '\n' || 380 buf->data[buf->len - 1] == '\r')) 381 svn_stringbuf_chop(buf, 1); 382 383 /* Determine the on-screen width of the line. */ 384 width = svn_utf_cstring_utf8_width(buf->data); 385 if (width == -1) 386 { 387 /* Determining the width failed. Try to get rid of unprintable 388 * characters in the line buffer. */ 389 buf = svn_stringbuf_create(svn_xml_fuzzy_escape(buf->data, pool), pool); 390 width = svn_utf_cstring_utf8_width(buf->data); 391 if (width == -1) 392 width = buf->len; /* fallback: buffer length */ 393 } 394 395 /* Trim further in case line is still too long, or add padding in case 396 * it is too short. */ 397 iterpool = svn_pool_create(pool); 398 while (width > line_width) 399 { 400 const char *last_valid; 401 402 svn_pool_clear(iterpool); 403 404 svn_stringbuf_chop(buf, 1); 405 406 /* Be careful not to invalidate the UTF-8 string by trimming 407 * just part of a character. */ 408 last_valid = svn_utf__last_valid(buf->data, buf->len); 409 if (last_valid < buf->data + buf->len) 410 svn_stringbuf_chop(buf, (buf->data + buf->len) - last_valid); 411 412 width = svn_utf_cstring_utf8_width(buf->data); 413 if (width == -1) 414 width = buf->len; /* fallback: buffer length */ 415 } 416 svn_pool_destroy(iterpool); 417 418 while (width == 0 || width < line_width) 419 { 420 svn_stringbuf_appendbyte(buf, ' '); 421 width++; 422 } 423 424 SVN_ERR_ASSERT_NO_RETURN(width == line_width); 425 return buf->data; 426} 427 428/* Merge CHUNK1 and CHUNK2 into a new chunk with conflict markers. */ 429static apr_array_header_t * 430merge_chunks_with_conflict_markers(apr_array_header_t *chunk1, 431 apr_array_header_t *chunk2, 432 apr_pool_t *result_pool) 433{ 434 apr_array_header_t *merged_chunk; 435 int i; 436 437 merged_chunk = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *)); 438 /* ### would be nice to show filenames next to conflict markers */ 439 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = 440 svn_stringbuf_create("<<<<<<<\n", result_pool); 441 for (i = 0; i < chunk1->nelts; i++) 442 { 443 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = 444 APR_ARRAY_IDX(chunk1, i, svn_stringbuf_t*); 445 } 446 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = 447 svn_stringbuf_create("=======\n", result_pool); 448 for (i = 0; i < chunk2->nelts; i++) 449 { 450 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = 451 APR_ARRAY_IDX(chunk2, i, svn_stringbuf_t*); 452 } 453 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = 454 svn_stringbuf_create(">>>>>>>\n", result_pool); 455 456 return merged_chunk; 457} 458 459/* Edit CHUNK and return the result in *MERGED_CHUNK allocated in POOL. */ 460static svn_error_t * 461edit_chunk(apr_array_header_t **merged_chunk, 462 apr_array_header_t *chunk, 463 const char *editor_cmd, 464 apr_hash_t *config, 465 apr_pool_t *result_pool, 466 apr_pool_t *scratch_pool) 467{ 468 apr_file_t *temp_file; 469 const char *temp_file_name; 470 int i; 471 apr_off_t pos; 472 svn_boolean_t eof; 473 svn_error_t *err; 474 apr_pool_t *iterpool; 475 476 SVN_ERR(svn_io_open_unique_file3(&temp_file, &temp_file_name, NULL, 477 svn_io_file_del_on_pool_cleanup, 478 scratch_pool, scratch_pool)); 479 iterpool = svn_pool_create(scratch_pool); 480 for (i = 0; i < chunk->nelts; i++) 481 { 482 svn_stringbuf_t *line = APR_ARRAY_IDX(chunk, i, svn_stringbuf_t *); 483 apr_size_t bytes_written; 484 485 svn_pool_clear(iterpool); 486 487 SVN_ERR(svn_io_file_write_full(temp_file, line->data, line->len, 488 &bytes_written, iterpool)); 489 if (line->len != bytes_written) 490 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, 491 _("Could not write data to temporary file")); 492 } 493 SVN_ERR(svn_io_file_flush_to_disk(temp_file, scratch_pool)); 494 495 err = svn_cmdline__edit_file_externally(temp_file_name, editor_cmd, 496 config, scratch_pool); 497 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)) 498 { 499 svn_error_t *root_err = svn_error_root_cause(err); 500 501 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 502 root_err->message ? root_err->message : 503 _("No editor found."))); 504 svn_error_clear(err); 505 *merged_chunk = NULL; 506 svn_pool_destroy(iterpool); 507 return SVN_NO_ERROR; 508 } 509 else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) 510 { 511 svn_error_t *root_err = svn_error_root_cause(err); 512 513 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 514 root_err->message ? root_err->message : 515 _("Error running editor."))); 516 svn_error_clear(err); 517 *merged_chunk = NULL; 518 svn_pool_destroy(iterpool); 519 return SVN_NO_ERROR; 520 } 521 else if (err) 522 return svn_error_trace(err); 523 524 *merged_chunk = apr_array_make(result_pool, 1, sizeof(svn_stringbuf_t *)); 525 pos = 0; 526 SVN_ERR(svn_io_file_seek(temp_file, APR_SET, &pos, scratch_pool)); 527 do 528 { 529 svn_stringbuf_t *line; 530 const char *eol_str; 531 532 svn_pool_clear(iterpool); 533 534 SVN_ERR(svn_io_file_readline(temp_file, &line, &eol_str, &eof, 535 APR_SIZE_MAX, result_pool, iterpool)); 536 if (eol_str) 537 svn_stringbuf_appendcstr(line, eol_str); 538 539 APR_ARRAY_PUSH(*merged_chunk, svn_stringbuf_t *) = line; 540 } 541 while (!eof); 542 svn_pool_destroy(iterpool); 543 544 SVN_ERR(svn_io_file_close(temp_file, scratch_pool)); 545 546 return SVN_NO_ERROR; 547} 548 549/* Create a separator string of the appropriate length. */ 550static const char * 551get_sep_string(apr_pool_t *result_pool) 552{ 553 int line_width = LINE_DISPLAY_WIDTH; 554 int i; 555 svn_stringbuf_t *buf; 556 557 buf = svn_stringbuf_create_empty(result_pool); 558 for (i = 0; i < line_width; i++) 559 svn_stringbuf_appendbyte(buf, '-'); 560 svn_stringbuf_appendbyte(buf, '+'); 561 for (i = 0; i < line_width; i++) 562 svn_stringbuf_appendbyte(buf, '-'); 563 svn_stringbuf_appendbyte(buf, '\n'); 564 565 return buf->data; 566} 567 568/* Merge chunks CHUNK1 and CHUNK2. 569 * Each lines array contains elements of type svn_stringbuf_t*. 570 * Return the result in *MERGED_CHUNK, or set *MERGED_CHUNK to NULL in 571 * case the user chooses to postpone resolution of this chunk. 572 * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */ 573static svn_error_t * 574merge_chunks(apr_array_header_t **merged_chunk, 575 svn_boolean_t *abort_merge, 576 apr_array_header_t *chunk1, 577 apr_array_header_t *chunk2, 578 svn_linenum_t current_line1, 579 svn_linenum_t current_line2, 580 const char *editor_cmd, 581 apr_hash_t *config, 582 apr_pool_t *result_pool, 583 apr_pool_t *scratch_pool) 584{ 585 svn_stringbuf_t *prompt; 586 int i; 587 int max_chunk_lines; 588 apr_pool_t *iterpool; 589 590 max_chunk_lines = chunk1->nelts > chunk2->nelts ? chunk1->nelts 591 : chunk2->nelts; 592 *abort_merge = FALSE; 593 594 /* 595 * Prepare the selection prompt. 596 */ 597 598 prompt = svn_stringbuf_create( 599 apr_psprintf(scratch_pool, "%s\n%s|%s\n%s", 600 _("Conflicting section found during merge:"), 601 prepare_line_for_display( 602 apr_psprintf(scratch_pool, 603 _("(1) their version (at line %lu)"), 604 current_line1), 605 scratch_pool), 606 prepare_line_for_display( 607 apr_psprintf(scratch_pool, 608 _("(2) your version (at line %lu)"), 609 current_line2), 610 scratch_pool), 611 get_sep_string(scratch_pool)), 612 scratch_pool); 613 614 iterpool = svn_pool_create(scratch_pool); 615 for (i = 0; i < max_chunk_lines; i++) 616 { 617 const char *line1; 618 const char *line2; 619 const char *prompt_line; 620 621 svn_pool_clear(iterpool); 622 623 if (i < chunk1->nelts) 624 { 625 svn_stringbuf_t *line_utf8; 626 627 SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8, 628 APR_ARRAY_IDX(chunk1, i, 629 svn_stringbuf_t*), 630 iterpool)); 631 line1 = prepare_line_for_display(line_utf8->data, iterpool); 632 } 633 else 634 line1 = prepare_line_for_display("", iterpool); 635 636 if (i < chunk2->nelts) 637 { 638 svn_stringbuf_t *line_utf8; 639 640 SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8, 641 APR_ARRAY_IDX(chunk2, i, 642 svn_stringbuf_t*), 643 iterpool)); 644 line2 = prepare_line_for_display(line_utf8->data, iterpool); 645 } 646 else 647 line2 = prepare_line_for_display("", iterpool); 648 649 prompt_line = apr_psprintf(iterpool, "%s|%s\n", line1, line2); 650 651 svn_stringbuf_appendcstr(prompt, prompt_line); 652 } 653 654 svn_stringbuf_appendcstr(prompt, get_sep_string(scratch_pool)); 655 svn_stringbuf_appendcstr( 656 prompt, 657 _("Select: (1) use their version, (2) use your version,\n" 658 " (12) their version first, then yours,\n" 659 " (21) your version first, then theirs,\n" 660 " (e1) edit their version and use the result,\n" 661 " (e2) edit your version and use the result,\n" 662 " (eb) edit both versions and use the result,\n" 663 " (p) postpone this conflicting section leaving conflict markers,\n" 664 " (a) abort file merge and return to main menu: ")); 665 666 /* Now let's see what the user wants to do with this conflict. */ 667 while (TRUE) 668 { 669 const char *answer; 670 671 svn_pool_clear(iterpool); 672 673 SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt->data, NULL, iterpool)); 674 if (strcmp(answer, "1") == 0) 675 { 676 *merged_chunk = chunk1; 677 break; 678 } 679 else if (strcmp(answer, "2") == 0) 680 { 681 *merged_chunk = chunk2; 682 break; 683 } 684 if (strcmp(answer, "12") == 0) 685 { 686 *merged_chunk = apr_array_make(result_pool, 687 chunk1->nelts + chunk2->nelts, 688 sizeof(svn_stringbuf_t *)); 689 apr_array_cat(*merged_chunk, chunk1); 690 apr_array_cat(*merged_chunk, chunk2); 691 break; 692 } 693 if (strcmp(answer, "21") == 0) 694 { 695 *merged_chunk = apr_array_make(result_pool, 696 chunk1->nelts + chunk2->nelts, 697 sizeof(svn_stringbuf_t *)); 698 apr_array_cat(*merged_chunk, chunk2); 699 apr_array_cat(*merged_chunk, chunk1); 700 break; 701 } 702 else if (strcmp(answer, "p") == 0) 703 { 704 *merged_chunk = NULL; 705 break; 706 } 707 else if (strcmp(answer, "e1") == 0) 708 { 709 SVN_ERR(edit_chunk(merged_chunk, chunk1, editor_cmd, config, 710 result_pool, iterpool)); 711 if (*merged_chunk) 712 break; 713 } 714 else if (strcmp(answer, "e2") == 0) 715 { 716 SVN_ERR(edit_chunk(merged_chunk, chunk2, editor_cmd, config, 717 result_pool, iterpool)); 718 if (*merged_chunk) 719 break; 720 } 721 else if (strcmp(answer, "eb") == 0) 722 { 723 apr_array_header_t *conflict_chunk; 724 725 conflict_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2, 726 scratch_pool); 727 SVN_ERR(edit_chunk(merged_chunk, conflict_chunk, editor_cmd, config, 728 result_pool, iterpool)); 729 if (*merged_chunk) 730 break; 731 } 732 else if (strcmp(answer, "a") == 0) 733 { 734 *abort_merge = TRUE; 735 break; 736 } 737 } 738 svn_pool_destroy(iterpool); 739 740 return SVN_NO_ERROR; 741} 742 743/* Perform a merge of chunks from FILE1 and FILE2, specified by START1/LEN1 744 * and START2/LEN2, respectively. Append the result to MERGED_FILE. 745 * The current line numbers for FILE1 and FILE2 are passed in *CURRENT_LINE1 746 * and *CURRENT_LINE2, and will be updated to new values upon return. 747 * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */ 748static svn_error_t * 749merge_file_chunks(svn_boolean_t *remains_in_conflict, 750 svn_boolean_t *abort_merge, 751 apr_file_t *merged_file, 752 apr_file_t *file1, 753 apr_file_t *file2, 754 apr_off_t start1, 755 apr_off_t len1, 756 apr_off_t start2, 757 apr_off_t len2, 758 svn_linenum_t *current_line1, 759 svn_linenum_t *current_line2, 760 const char *editor_cmd, 761 apr_hash_t *config, 762 apr_pool_t *scratch_pool) 763{ 764 apr_array_header_t *chunk1; 765 apr_array_header_t *chunk2; 766 apr_array_header_t *merged_chunk; 767 apr_pool_t *iterpool; 768 int i; 769 770 SVN_ERR(read_diff_chunk(&chunk1, current_line1, file1, *current_line1, 771 start1, len1, scratch_pool, scratch_pool)); 772 SVN_ERR(read_diff_chunk(&chunk2, current_line2, file2, *current_line2, 773 start2, len2, scratch_pool, scratch_pool)); 774 775 SVN_ERR(merge_chunks(&merged_chunk, abort_merge, chunk1, chunk2, 776 *current_line1, *current_line2, 777 editor_cmd, config, 778 scratch_pool, scratch_pool)); 779 780 if (*abort_merge) 781 return SVN_NO_ERROR; 782 783 /* If the user chose 'postpone' put conflict markers and left/right 784 * versions into the merged file. */ 785 if (merged_chunk == NULL) 786 { 787 *remains_in_conflict = TRUE; 788 merged_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2, 789 scratch_pool); 790 } 791 792 iterpool = svn_pool_create(scratch_pool); 793 for (i = 0; i < merged_chunk->nelts; i++) 794 { 795 apr_size_t bytes_written; 796 svn_stringbuf_t *line = APR_ARRAY_IDX(merged_chunk, i, 797 svn_stringbuf_t *); 798 799 svn_pool_clear(iterpool); 800 801 SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len, 802 &bytes_written, iterpool)); 803 if (line->len != bytes_written) 804 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, 805 _("Could not write data to merged file")); 806 } 807 svn_pool_destroy(iterpool); 808 809 return SVN_NO_ERROR; 810} 811 812/* Original, modified, and latest all differ from one another. 813 * This is a conflict and we'll need to ask the user to merge it. */ 814static svn_error_t * 815file_merge_output_conflict(void *output_baton, 816 apr_off_t original_start, 817 apr_off_t original_length, 818 apr_off_t modified_start, 819 apr_off_t modified_length, 820 apr_off_t latest_start, 821 apr_off_t latest_length, 822 svn_diff_t *resolved_diff) 823{ 824 struct file_merge_baton *b = output_baton; 825 826 if (b->abort_merge) 827 return SVN_NO_ERROR; 828 829 SVN_ERR(merge_file_chunks(&b->remains_in_conflict, 830 &b->abort_merge, 831 b->merged_file, 832 b->modified_file, 833 b->latest_file, 834 modified_start, 835 modified_length, 836 latest_start, 837 latest_length, 838 &b->current_line_modified, 839 &b->current_line_latest, 840 b->editor_cmd, 841 b->config, 842 b->scratch_pool)); 843 return SVN_NO_ERROR; 844} 845 846/* Our collection of diff output functions that get driven during the merge. */ 847static svn_diff_output_fns_t file_merge_diff_output_fns = { 848 file_merge_output_common, 849 file_merge_output_diff_modified, 850 file_merge_output_diff_latest, 851 file_merge_output_diff_common, 852 file_merge_output_conflict 853}; 854 855svn_error_t * 856svn_cl__merge_file(const char *base_path, 857 const char *their_path, 858 const char *my_path, 859 const char *merged_path, 860 const char *wc_path, 861 const char *path_prefix, 862 const char *editor_cmd, 863 apr_hash_t *config, 864 svn_boolean_t *remains_in_conflict, 865 apr_pool_t *scratch_pool) 866{ 867 svn_diff_t *diff; 868 svn_diff_file_options_t *diff_options; 869 apr_file_t *original_file; 870 apr_file_t *modified_file; 871 apr_file_t *latest_file; 872 apr_file_t *merged_file; 873 const char *merged_file_name; 874 struct file_merge_baton fmb; 875 svn_boolean_t executable; 876 const char *merged_path_local_style; 877 const char *merged_rel_path; 878 const char *wc_path_local_style; 879 const char *wc_rel_path = svn_dirent_skip_ancestor(path_prefix, wc_path); 880 881 /* PATH_PREFIX may not be an ancestor of WC_PATH, just use the 882 full WC_PATH in that case. */ 883 if (wc_rel_path) 884 wc_path_local_style = svn_dirent_local_style(wc_rel_path, scratch_pool); 885 else 886 wc_path_local_style = svn_dirent_local_style(wc_path, scratch_pool); 887 888 SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merging '%s'.\n"), 889 wc_path_local_style)); 890 891 SVN_ERR(svn_io_file_open(&original_file, base_path, 892 APR_READ | APR_BUFFERED, 893 APR_OS_DEFAULT, scratch_pool)); 894 SVN_ERR(svn_io_file_open(&modified_file, their_path, 895 APR_READ | APR_BUFFERED, 896 APR_OS_DEFAULT, scratch_pool)); 897 SVN_ERR(svn_io_file_open(&latest_file, my_path, 898 APR_READ | APR_BUFFERED, 899 APR_OS_DEFAULT, scratch_pool)); 900 SVN_ERR(svn_io_open_unique_file3(&merged_file, &merged_file_name, 901 NULL, svn_io_file_del_none, 902 scratch_pool, scratch_pool)); 903 904 diff_options = svn_diff_file_options_create(scratch_pool); 905 SVN_ERR(svn_diff_file_diff3_2(&diff, base_path, their_path, my_path, 906 diff_options, scratch_pool)); 907 908 fmb.original_file = original_file; 909 fmb.modified_file = modified_file; 910 fmb.latest_file = latest_file; 911 fmb.current_line_original = 0; 912 fmb.current_line_modified = 0; 913 fmb.current_line_latest = 0; 914 fmb.merged_file = merged_file; 915 fmb.remains_in_conflict = FALSE; 916 fmb.editor_cmd = editor_cmd; 917 fmb.config = config; 918 fmb.abort_merge = FALSE; 919 fmb.scratch_pool = scratch_pool; 920 921 SVN_ERR(svn_diff_output(diff, &fmb, &file_merge_diff_output_fns)); 922 923 SVN_ERR(svn_io_file_close(original_file, scratch_pool)); 924 SVN_ERR(svn_io_file_close(modified_file, scratch_pool)); 925 SVN_ERR(svn_io_file_close(latest_file, scratch_pool)); 926 SVN_ERR(svn_io_file_close(merged_file, scratch_pool)); 927 928 /* Start out assuming that conflicts remain. */ 929 if (remains_in_conflict) 930 *remains_in_conflict = TRUE; 931 932 if (fmb.abort_merge) 933 { 934 SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool)); 935 SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merge of '%s' aborted.\n"), 936 wc_path_local_style)); 937 return SVN_NO_ERROR; 938 } 939 940 SVN_ERR(svn_io_is_file_executable(&executable, merged_path, scratch_pool)); 941 942 merged_rel_path = svn_dirent_skip_ancestor(path_prefix, merged_path); 943 if (merged_rel_path) 944 merged_path_local_style = svn_dirent_local_style(merged_rel_path, 945 scratch_pool); 946 else 947 merged_path_local_style = svn_dirent_local_style(merged_path, 948 scratch_pool); 949 950 SVN_ERR_W(svn_io_copy_file(merged_file_name, merged_path, FALSE, 951 scratch_pool), 952 apr_psprintf(scratch_pool, 953 _("Could not write merged result to '%s', saved " 954 "instead at '%s'.\n'%s' remains in conflict.\n"), 955 merged_path_local_style, 956 svn_dirent_local_style(merged_file_name, 957 scratch_pool), 958 wc_path_local_style)); 959 SVN_ERR(svn_io_set_file_executable(merged_path, executable, FALSE, 960 scratch_pool)); 961 SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool)); 962 963 /* The merge was not aborted and we could install the merged result. The 964 * file remains in conflict unless all conflicting sections were resolved. */ 965 if (remains_in_conflict) 966 *remains_in_conflict = fmb.remains_in_conflict; 967 968 if (fmb.remains_in_conflict) 969 SVN_ERR(svn_cmdline_printf( 970 scratch_pool, 971 _("Merge of '%s' completed (remains in conflict).\n"), 972 wc_path_local_style)); 973 else 974 SVN_ERR(svn_cmdline_printf( 975 scratch_pool, _("Merge of '%s' completed.\n"), 976 wc_path_local_style)); 977 978 return SVN_NO_ERROR; 979} 980