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