1251881Speter/* 2251881Speter * file-merge.c: internal file merge tool 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* This is an interactive file merge tool with an interface similar to 25251881Speter * the interactive mode of the UNIX sdiff ("side-by-side diff") utility. 26251881Speter * The merge tool is driven by Subversion's diff code and user input. */ 27251881Speter 28251881Speter#include "svn_cmdline.h" 29251881Speter#include "svn_dirent_uri.h" 30251881Speter#include "svn_error.h" 31251881Speter#include "svn_pools.h" 32251881Speter#include "svn_io.h" 33251881Speter#include "svn_utf.h" 34251881Speter#include "svn_xml.h" 35251881Speter 36251881Speter#include "cl.h" 37251881Speter 38251881Speter#include "svn_private_config.h" 39251881Speter#include "private/svn_utf_private.h" 40251881Speter#include "private/svn_cmdline_private.h" 41251881Speter#include "private/svn_dep_compat.h" 42251881Speter 43251881Speter#if APR_HAVE_SYS_IOCTL_H 44251881Speter#include <sys/ioctl.h> 45251881Speter#endif 46251881Speter 47251881Speter#if APR_HAVE_UNISTD_H 48251881Speter#include <unistd.h> 49251881Speter#endif 50251881Speter 51251881Speter#include <fcntl.h> 52251881Speter#include <stdlib.h> 53251881Speter 54251881Speter/* Baton for functions in this file which implement svn_diff_output_fns_t. */ 55251881Speterstruct file_merge_baton { 56251881Speter /* The files being merged. */ 57251881Speter apr_file_t *original_file; 58251881Speter apr_file_t *modified_file; 59251881Speter apr_file_t *latest_file; 60251881Speter 61251881Speter /* Counters to keep track of the current line in each file. */ 62251881Speter svn_linenum_t current_line_original; 63251881Speter svn_linenum_t current_line_modified; 64251881Speter svn_linenum_t current_line_latest; 65251881Speter 66251881Speter /* The merge result is written to this file. */ 67251881Speter apr_file_t *merged_file; 68251881Speter 69251881Speter /* Whether the merged file remains in conflict after the merge. */ 70251881Speter svn_boolean_t remains_in_conflict; 71251881Speter 72251881Speter /* External editor command for editing chunks. */ 73251881Speter const char *editor_cmd; 74251881Speter 75251881Speter /* The client configuration hash. */ 76251881Speter apr_hash_t *config; 77251881Speter 78251881Speter /* Wether the merge should be aborted. */ 79251881Speter svn_boolean_t abort_merge; 80251881Speter 81251881Speter /* Pool for temporary allocations. */ 82251881Speter apr_pool_t *scratch_pool; 83251881Speter} file_merge_baton; 84251881Speter 85251881Speter/* Copy LEN lines from SOURCE_FILE to the MERGED_FILE, starting at 86251881Speter * line START. The CURRENT_LINE is the current line in the source file. 87251881Speter * The new current line is returned in *NEW_CURRENT_LINE. */ 88251881Speterstatic svn_error_t * 89251881Spetercopy_to_merged_file(svn_linenum_t *new_current_line, 90251881Speter apr_file_t *merged_file, 91251881Speter apr_file_t *source_file, 92251881Speter apr_off_t start, 93251881Speter apr_off_t len, 94251881Speter svn_linenum_t current_line, 95251881Speter apr_pool_t *scratch_pool) 96251881Speter{ 97251881Speter apr_pool_t *iterpool; 98251881Speter svn_stringbuf_t *line; 99251881Speter apr_size_t lines_read; 100251881Speter apr_size_t lines_copied; 101251881Speter svn_boolean_t eof; 102251881Speter svn_linenum_t orig_current_line = current_line; 103251881Speter 104251881Speter lines_read = 0; 105251881Speter iterpool = svn_pool_create(scratch_pool); 106251881Speter while (current_line < start) 107251881Speter { 108251881Speter svn_pool_clear(iterpool); 109251881Speter 110251881Speter SVN_ERR(svn_io_file_readline(source_file, &line, NULL, &eof, 111251881Speter APR_SIZE_MAX, iterpool, iterpool)); 112251881Speter if (eof) 113251881Speter break; 114251881Speter 115251881Speter current_line++; 116251881Speter lines_read++; 117251881Speter } 118251881Speter 119251881Speter lines_copied = 0; 120251881Speter while (lines_copied < len) 121251881Speter { 122251881Speter apr_size_t bytes_written; 123251881Speter const char *eol_str; 124251881Speter 125251881Speter svn_pool_clear(iterpool); 126251881Speter 127251881Speter SVN_ERR(svn_io_file_readline(source_file, &line, &eol_str, &eof, 128251881Speter APR_SIZE_MAX, iterpool, iterpool)); 129251881Speter if (eol_str) 130251881Speter svn_stringbuf_appendcstr(line, eol_str); 131251881Speter SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len, 132251881Speter &bytes_written, iterpool)); 133251881Speter if (bytes_written != line->len) 134251881Speter return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, 135251881Speter _("Could not write data to merged file")); 136251881Speter if (eof) 137251881Speter break; 138251881Speter lines_copied++; 139251881Speter } 140251881Speter svn_pool_destroy(iterpool); 141251881Speter 142251881Speter *new_current_line = orig_current_line + lines_read + lines_copied; 143251881Speter 144251881Speter return SVN_NO_ERROR; 145251881Speter} 146251881Speter 147251881Speter/* Copy common data to the merged file. */ 148251881Speterstatic svn_error_t * 149251881Speterfile_merge_output_common(void *output_baton, 150251881Speter apr_off_t original_start, 151251881Speter apr_off_t original_length, 152251881Speter apr_off_t modified_start, 153251881Speter apr_off_t modified_length, 154251881Speter apr_off_t latest_start, 155251881Speter apr_off_t latest_length) 156251881Speter{ 157251881Speter struct file_merge_baton *b = output_baton; 158251881Speter 159251881Speter if (b->abort_merge) 160251881Speter return SVN_NO_ERROR; 161251881Speter 162251881Speter SVN_ERR(copy_to_merged_file(&b->current_line_original, 163251881Speter b->merged_file, 164251881Speter b->original_file, 165251881Speter original_start, 166251881Speter original_length, 167251881Speter b->current_line_original, 168251881Speter b->scratch_pool)); 169251881Speter return SVN_NO_ERROR; 170251881Speter} 171251881Speter 172251881Speter/* Original/latest match up, but modified differs. 173251881Speter * Copy modified data to the merged file. */ 174251881Speterstatic svn_error_t * 175251881Speterfile_merge_output_diff_modified(void *output_baton, 176251881Speter apr_off_t original_start, 177251881Speter apr_off_t original_length, 178251881Speter apr_off_t modified_start, 179251881Speter apr_off_t modified_length, 180251881Speter apr_off_t latest_start, 181251881Speter apr_off_t latest_length) 182251881Speter{ 183251881Speter struct file_merge_baton *b = output_baton; 184251881Speter 185251881Speter if (b->abort_merge) 186251881Speter return SVN_NO_ERROR; 187251881Speter 188251881Speter SVN_ERR(copy_to_merged_file(&b->current_line_modified, 189251881Speter b->merged_file, 190251881Speter b->modified_file, 191251881Speter modified_start, 192251881Speter modified_length, 193251881Speter b->current_line_modified, 194251881Speter b->scratch_pool)); 195251881Speter 196251881Speter return SVN_NO_ERROR; 197251881Speter} 198251881Speter 199251881Speter/* Original/modified match up, but latest differs. 200251881Speter * Copy latest data to the merged file. */ 201251881Speterstatic svn_error_t * 202251881Speterfile_merge_output_diff_latest(void *output_baton, 203251881Speter apr_off_t original_start, 204251881Speter apr_off_t original_length, 205251881Speter apr_off_t modified_start, 206251881Speter apr_off_t modified_length, 207251881Speter apr_off_t latest_start, 208251881Speter apr_off_t latest_length) 209251881Speter{ 210251881Speter struct file_merge_baton *b = output_baton; 211251881Speter 212251881Speter if (b->abort_merge) 213251881Speter return SVN_NO_ERROR; 214251881Speter 215251881Speter SVN_ERR(copy_to_merged_file(&b->current_line_latest, 216251881Speter b->merged_file, 217251881Speter b->latest_file, 218251881Speter latest_start, 219251881Speter latest_length, 220251881Speter b->current_line_latest, 221251881Speter b->scratch_pool)); 222251881Speter 223251881Speter return SVN_NO_ERROR; 224251881Speter} 225251881Speter 226251881Speter/* Modified/latest match up, but original differs. 227251881Speter * Copy latest data to the merged file. */ 228251881Speterstatic svn_error_t * 229251881Speterfile_merge_output_diff_common(void *output_baton, 230251881Speter apr_off_t original_start, 231251881Speter apr_off_t original_length, 232251881Speter apr_off_t modified_start, 233251881Speter apr_off_t modified_length, 234251881Speter apr_off_t latest_start, 235251881Speter apr_off_t latest_length) 236251881Speter{ 237251881Speter struct file_merge_baton *b = output_baton; 238251881Speter 239251881Speter if (b->abort_merge) 240251881Speter return SVN_NO_ERROR; 241251881Speter 242251881Speter SVN_ERR(copy_to_merged_file(&b->current_line_latest, 243251881Speter b->merged_file, 244251881Speter b->latest_file, 245251881Speter latest_start, 246251881Speter latest_length, 247251881Speter b->current_line_latest, 248251881Speter b->scratch_pool)); 249251881Speter return SVN_NO_ERROR; 250251881Speter} 251251881Speter 252251881Speter 253251881Speter/* Return LEN lines within the diff chunk staring at line START 254251881Speter * in a *LINES array of svn_stringbuf_t* elements. 255251881Speter * Store the resulting current in in *NEW_CURRENT_LINE. */ 256251881Speterstatic svn_error_t * 257251881Speterread_diff_chunk(apr_array_header_t **lines, 258251881Speter svn_linenum_t *new_current_line, 259251881Speter apr_file_t *file, 260251881Speter svn_linenum_t current_line, 261251881Speter apr_off_t start, 262251881Speter apr_off_t len, 263251881Speter apr_pool_t *result_pool, 264251881Speter apr_pool_t *scratch_pool) 265251881Speter{ 266251881Speter svn_stringbuf_t *line; 267251881Speter const char *eol_str; 268251881Speter svn_boolean_t eof; 269251881Speter apr_pool_t *iterpool; 270251881Speter 271251881Speter *lines = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *)); 272251881Speter 273251881Speter /* Skip lines before start of range. */ 274251881Speter iterpool = svn_pool_create(scratch_pool); 275251881Speter while (current_line < start) 276251881Speter { 277251881Speter svn_pool_clear(iterpool); 278251881Speter SVN_ERR(svn_io_file_readline(file, &line, NULL, &eof, APR_SIZE_MAX, 279251881Speter iterpool, iterpool)); 280251881Speter if (eof) 281251881Speter return SVN_NO_ERROR; 282251881Speter current_line++; 283251881Speter } 284251881Speter svn_pool_destroy(iterpool); 285251881Speter 286251881Speter /* Now read the lines. */ 287251881Speter do 288251881Speter { 289251881Speter SVN_ERR(svn_io_file_readline(file, &line, &eol_str, &eof, APR_SIZE_MAX, 290251881Speter result_pool, scratch_pool)); 291251881Speter if (eol_str) 292251881Speter svn_stringbuf_appendcstr(line, eol_str); 293251881Speter APR_ARRAY_PUSH(*lines, svn_stringbuf_t *) = line; 294251881Speter if (eof) 295251881Speter break; 296251881Speter current_line++; 297251881Speter } 298251881Speter while ((*lines)->nelts < len); 299251881Speter 300251881Speter *new_current_line = current_line; 301251881Speter 302251881Speter return SVN_NO_ERROR; 303251881Speter} 304251881Speter 305251881Speter/* Return the terminal width in number of columns. */ 306251881Speterstatic int 307251881Speterget_term_width(void) 308251881Speter{ 309251881Speter char *columns_env; 310251881Speter#ifdef TIOCGWINSZ 311251881Speter int fd; 312251881Speter 313251881Speter fd = open("/dev/tty", O_RDONLY, 0); 314251881Speter if (fd != -1) 315251881Speter { 316251881Speter struct winsize ws; 317251881Speter int error; 318251881Speter 319251881Speter error = ioctl(fd, TIOCGWINSZ, &ws); 320251881Speter close(fd); 321251881Speter if (error != -1) 322251881Speter { 323251881Speter if (ws.ws_col < 80) 324251881Speter return 80; 325251881Speter return ws.ws_col; 326251881Speter } 327251881Speter } 328251881Speter#elif defined WIN32 329251881Speter CONSOLE_SCREEN_BUFFER_INFO csbi; 330251881Speter 331251881Speter if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) 332251881Speter { 333251881Speter if (csbi.dwSize.X < 80) 334251881Speter return 80; 335251881Speter return csbi.dwSize.X; 336251881Speter } 337251881Speter#endif 338251881Speter 339251881Speter columns_env = getenv("COLUMNS"); 340251881Speter if (columns_env) 341251881Speter { 342251881Speter svn_error_t *err; 343251881Speter int cols; 344251881Speter 345251881Speter err = svn_cstring_atoi(&cols, columns_env); 346251881Speter if (err) 347251881Speter { 348251881Speter svn_error_clear(err); 349251881Speter return 80; 350251881Speter } 351251881Speter 352251881Speter if (cols < 80) 353251881Speter return 80; 354251881Speter return cols; 355251881Speter } 356251881Speter else 357251881Speter return 80; 358251881Speter} 359251881Speter 360251881Speter#define LINE_DISPLAY_WIDTH ((get_term_width() / 2) - 2) 361251881Speter 362251881Speter/* Prepare LINE for display, pruning or extending it to an appropriate 363251881Speter * display width, and stripping the EOL marker, if any. 364251881Speter * This function assumes that the data in LINE is encoded in UTF-8. */ 365251881Speterstatic const char * 366251881Speterprepare_line_for_display(const char *line, apr_pool_t *pool) 367251881Speter{ 368251881Speter svn_stringbuf_t *buf = svn_stringbuf_create(line, pool); 369251881Speter size_t width; 370251881Speter size_t line_width = LINE_DISPLAY_WIDTH; 371251881Speter apr_pool_t *iterpool; 372251881Speter 373251881Speter /* Trim EOL. */ 374251881Speter if (buf->len >= 2 && 375251881Speter buf->data[buf->len - 2] == '\r' && 376251881Speter buf->data[buf->len - 1] == '\n') 377251881Speter svn_stringbuf_chop(buf, 2); 378251881Speter else if (buf->len >= 1 && 379251881Speter (buf->data[buf->len - 1] == '\n' || 380251881Speter buf->data[buf->len - 1] == '\r')) 381251881Speter svn_stringbuf_chop(buf, 1); 382251881Speter 383251881Speter /* Determine the on-screen width of the line. */ 384251881Speter width = svn_utf_cstring_utf8_width(buf->data); 385251881Speter if (width == -1) 386251881Speter { 387251881Speter /* Determining the width failed. Try to get rid of unprintable 388251881Speter * characters in the line buffer. */ 389251881Speter buf = svn_stringbuf_create(svn_xml_fuzzy_escape(buf->data, pool), pool); 390251881Speter width = svn_utf_cstring_utf8_width(buf->data); 391251881Speter if (width == -1) 392251881Speter width = buf->len; /* fallback: buffer length */ 393251881Speter } 394251881Speter 395251881Speter /* Trim further in case line is still too long, or add padding in case 396251881Speter * it is too short. */ 397251881Speter iterpool = svn_pool_create(pool); 398251881Speter while (width > line_width) 399251881Speter { 400251881Speter const char *last_valid; 401251881Speter 402251881Speter svn_pool_clear(iterpool); 403251881Speter 404251881Speter svn_stringbuf_chop(buf, 1); 405251881Speter 406251881Speter /* Be careful not to invalidate the UTF-8 string by trimming 407251881Speter * just part of a character. */ 408251881Speter last_valid = svn_utf__last_valid(buf->data, buf->len); 409251881Speter if (last_valid < buf->data + buf->len) 410251881Speter svn_stringbuf_chop(buf, (buf->data + buf->len) - last_valid); 411251881Speter 412251881Speter width = svn_utf_cstring_utf8_width(buf->data); 413251881Speter if (width == -1) 414251881Speter width = buf->len; /* fallback: buffer length */ 415251881Speter } 416251881Speter svn_pool_destroy(iterpool); 417251881Speter 418251881Speter while (width == 0 || width < line_width) 419251881Speter { 420251881Speter svn_stringbuf_appendbyte(buf, ' '); 421251881Speter width++; 422251881Speter } 423251881Speter 424251881Speter SVN_ERR_ASSERT_NO_RETURN(width == line_width); 425251881Speter return buf->data; 426251881Speter} 427251881Speter 428251881Speter/* Merge CHUNK1 and CHUNK2 into a new chunk with conflict markers. */ 429251881Speterstatic apr_array_header_t * 430251881Spetermerge_chunks_with_conflict_markers(apr_array_header_t *chunk1, 431251881Speter apr_array_header_t *chunk2, 432251881Speter apr_pool_t *result_pool) 433251881Speter{ 434251881Speter apr_array_header_t *merged_chunk; 435251881Speter int i; 436251881Speter 437251881Speter merged_chunk = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *)); 438251881Speter /* ### would be nice to show filenames next to conflict markers */ 439251881Speter APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = 440251881Speter svn_stringbuf_create("<<<<<<<\n", result_pool); 441251881Speter for (i = 0; i < chunk1->nelts; i++) 442251881Speter { 443251881Speter APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = 444251881Speter APR_ARRAY_IDX(chunk1, i, svn_stringbuf_t*); 445251881Speter } 446251881Speter APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = 447251881Speter svn_stringbuf_create("=======\n", result_pool); 448251881Speter for (i = 0; i < chunk2->nelts; i++) 449251881Speter { 450251881Speter APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = 451251881Speter APR_ARRAY_IDX(chunk2, i, svn_stringbuf_t*); 452251881Speter } 453251881Speter APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = 454251881Speter svn_stringbuf_create(">>>>>>>\n", result_pool); 455251881Speter 456251881Speter return merged_chunk; 457251881Speter} 458251881Speter 459251881Speter/* Edit CHUNK and return the result in *MERGED_CHUNK allocated in POOL. */ 460251881Speterstatic svn_error_t * 461251881Speteredit_chunk(apr_array_header_t **merged_chunk, 462251881Speter apr_array_header_t *chunk, 463251881Speter const char *editor_cmd, 464251881Speter apr_hash_t *config, 465251881Speter apr_pool_t *result_pool, 466251881Speter apr_pool_t *scratch_pool) 467251881Speter{ 468251881Speter apr_file_t *temp_file; 469251881Speter const char *temp_file_name; 470251881Speter int i; 471251881Speter apr_off_t pos; 472251881Speter svn_boolean_t eof; 473251881Speter svn_error_t *err; 474251881Speter apr_pool_t *iterpool; 475251881Speter 476251881Speter SVN_ERR(svn_io_open_unique_file3(&temp_file, &temp_file_name, NULL, 477251881Speter svn_io_file_del_on_pool_cleanup, 478251881Speter scratch_pool, scratch_pool)); 479251881Speter iterpool = svn_pool_create(scratch_pool); 480251881Speter for (i = 0; i < chunk->nelts; i++) 481251881Speter { 482251881Speter svn_stringbuf_t *line = APR_ARRAY_IDX(chunk, i, svn_stringbuf_t *); 483251881Speter apr_size_t bytes_written; 484251881Speter 485251881Speter svn_pool_clear(iterpool); 486251881Speter 487251881Speter SVN_ERR(svn_io_file_write_full(temp_file, line->data, line->len, 488251881Speter &bytes_written, iterpool)); 489251881Speter if (line->len != bytes_written) 490251881Speter return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, 491251881Speter _("Could not write data to temporary file")); 492251881Speter } 493251881Speter SVN_ERR(svn_io_file_flush_to_disk(temp_file, scratch_pool)); 494251881Speter 495251881Speter err = svn_cmdline__edit_file_externally(temp_file_name, editor_cmd, 496251881Speter config, scratch_pool); 497251881Speter if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)) 498251881Speter { 499251881Speter svn_error_t *root_err = svn_error_root_cause(err); 500251881Speter 501251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 502251881Speter root_err->message ? root_err->message : 503251881Speter _("No editor found."))); 504251881Speter svn_error_clear(err); 505251881Speter *merged_chunk = NULL; 506251881Speter svn_pool_destroy(iterpool); 507251881Speter return SVN_NO_ERROR; 508251881Speter } 509251881Speter else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) 510251881Speter { 511251881Speter svn_error_t *root_err = svn_error_root_cause(err); 512251881Speter 513251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", 514251881Speter root_err->message ? root_err->message : 515251881Speter _("Error running editor."))); 516251881Speter svn_error_clear(err); 517251881Speter *merged_chunk = NULL; 518251881Speter svn_pool_destroy(iterpool); 519251881Speter return SVN_NO_ERROR; 520251881Speter } 521251881Speter else if (err) 522251881Speter return svn_error_trace(err); 523251881Speter 524251881Speter *merged_chunk = apr_array_make(result_pool, 1, sizeof(svn_stringbuf_t *)); 525251881Speter pos = 0; 526251881Speter SVN_ERR(svn_io_file_seek(temp_file, APR_SET, &pos, scratch_pool)); 527251881Speter do 528251881Speter { 529251881Speter svn_stringbuf_t *line; 530251881Speter const char *eol_str; 531251881Speter 532251881Speter svn_pool_clear(iterpool); 533251881Speter 534251881Speter SVN_ERR(svn_io_file_readline(temp_file, &line, &eol_str, &eof, 535251881Speter APR_SIZE_MAX, result_pool, iterpool)); 536251881Speter if (eol_str) 537251881Speter svn_stringbuf_appendcstr(line, eol_str); 538251881Speter 539251881Speter APR_ARRAY_PUSH(*merged_chunk, svn_stringbuf_t *) = line; 540251881Speter } 541251881Speter while (!eof); 542251881Speter svn_pool_destroy(iterpool); 543251881Speter 544251881Speter SVN_ERR(svn_io_file_close(temp_file, scratch_pool)); 545251881Speter 546251881Speter return SVN_NO_ERROR; 547251881Speter} 548251881Speter 549251881Speter/* Create a separator string of the appropriate length. */ 550251881Speterstatic const char * 551251881Speterget_sep_string(apr_pool_t *result_pool) 552251881Speter{ 553251881Speter int line_width = LINE_DISPLAY_WIDTH; 554251881Speter int i; 555251881Speter svn_stringbuf_t *buf; 556251881Speter 557251881Speter buf = svn_stringbuf_create_empty(result_pool); 558251881Speter for (i = 0; i < line_width; i++) 559251881Speter svn_stringbuf_appendbyte(buf, '-'); 560251881Speter svn_stringbuf_appendbyte(buf, '+'); 561251881Speter for (i = 0; i < line_width; i++) 562251881Speter svn_stringbuf_appendbyte(buf, '-'); 563251881Speter svn_stringbuf_appendbyte(buf, '\n'); 564251881Speter 565251881Speter return buf->data; 566251881Speter} 567251881Speter 568251881Speter/* Merge chunks CHUNK1 and CHUNK2. 569251881Speter * Each lines array contains elements of type svn_stringbuf_t*. 570251881Speter * Return the result in *MERGED_CHUNK, or set *MERGED_CHUNK to NULL in 571251881Speter * case the user chooses to postpone resolution of this chunk. 572251881Speter * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */ 573251881Speterstatic svn_error_t * 574251881Spetermerge_chunks(apr_array_header_t **merged_chunk, 575251881Speter svn_boolean_t *abort_merge, 576251881Speter apr_array_header_t *chunk1, 577251881Speter apr_array_header_t *chunk2, 578251881Speter svn_linenum_t current_line1, 579251881Speter svn_linenum_t current_line2, 580251881Speter const char *editor_cmd, 581251881Speter apr_hash_t *config, 582251881Speter apr_pool_t *result_pool, 583251881Speter apr_pool_t *scratch_pool) 584251881Speter{ 585251881Speter svn_stringbuf_t *prompt; 586251881Speter int i; 587251881Speter int max_chunk_lines; 588251881Speter apr_pool_t *iterpool; 589251881Speter 590251881Speter max_chunk_lines = chunk1->nelts > chunk2->nelts ? chunk1->nelts 591251881Speter : chunk2->nelts; 592251881Speter *abort_merge = FALSE; 593251881Speter 594251881Speter /* 595251881Speter * Prepare the selection prompt. 596251881Speter */ 597251881Speter 598251881Speter prompt = svn_stringbuf_create( 599251881Speter apr_psprintf(scratch_pool, "%s\n%s|%s\n%s", 600251881Speter _("Conflicting section found during merge:"), 601251881Speter prepare_line_for_display( 602251881Speter apr_psprintf(scratch_pool, 603251881Speter _("(1) their version (at line %lu)"), 604251881Speter current_line1), 605251881Speter scratch_pool), 606251881Speter prepare_line_for_display( 607251881Speter apr_psprintf(scratch_pool, 608251881Speter _("(2) your version (at line %lu)"), 609251881Speter current_line2), 610251881Speter scratch_pool), 611251881Speter get_sep_string(scratch_pool)), 612251881Speter scratch_pool); 613251881Speter 614251881Speter iterpool = svn_pool_create(scratch_pool); 615251881Speter for (i = 0; i < max_chunk_lines; i++) 616251881Speter { 617251881Speter const char *line1; 618251881Speter const char *line2; 619251881Speter const char *prompt_line; 620251881Speter 621251881Speter svn_pool_clear(iterpool); 622251881Speter 623251881Speter if (i < chunk1->nelts) 624251881Speter { 625251881Speter svn_stringbuf_t *line_utf8; 626251881Speter 627251881Speter SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8, 628251881Speter APR_ARRAY_IDX(chunk1, i, 629251881Speter svn_stringbuf_t*), 630251881Speter iterpool)); 631251881Speter line1 = prepare_line_for_display(line_utf8->data, iterpool); 632251881Speter } 633251881Speter else 634251881Speter line1 = prepare_line_for_display("", iterpool); 635251881Speter 636251881Speter if (i < chunk2->nelts) 637251881Speter { 638251881Speter svn_stringbuf_t *line_utf8; 639251881Speter 640251881Speter SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8, 641251881Speter APR_ARRAY_IDX(chunk2, i, 642251881Speter svn_stringbuf_t*), 643251881Speter iterpool)); 644251881Speter line2 = prepare_line_for_display(line_utf8->data, iterpool); 645251881Speter } 646251881Speter else 647251881Speter line2 = prepare_line_for_display("", iterpool); 648251881Speter 649251881Speter prompt_line = apr_psprintf(iterpool, "%s|%s\n", line1, line2); 650251881Speter 651251881Speter svn_stringbuf_appendcstr(prompt, prompt_line); 652251881Speter } 653251881Speter 654251881Speter svn_stringbuf_appendcstr(prompt, get_sep_string(scratch_pool)); 655251881Speter svn_stringbuf_appendcstr( 656251881Speter prompt, 657251881Speter _("Select: (1) use their version, (2) use your version,\n" 658253734Speter " (12) their version first, then yours,\n" 659253734Speter " (21) your version first, then theirs,\n" 660251881Speter " (e1) edit their version and use the result,\n" 661251881Speter " (e2) edit your version and use the result,\n" 662251881Speter " (eb) edit both versions and use the result,\n" 663251881Speter " (p) postpone this conflicting section leaving conflict markers,\n" 664251881Speter " (a) abort file merge and return to main menu: ")); 665251881Speter 666251881Speter /* Now let's see what the user wants to do with this conflict. */ 667251881Speter while (TRUE) 668251881Speter { 669251881Speter const char *answer; 670251881Speter 671251881Speter svn_pool_clear(iterpool); 672251881Speter 673251881Speter SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt->data, NULL, iterpool)); 674251881Speter if (strcmp(answer, "1") == 0) 675251881Speter { 676251881Speter *merged_chunk = chunk1; 677251881Speter break; 678251881Speter } 679251881Speter else if (strcmp(answer, "2") == 0) 680251881Speter { 681251881Speter *merged_chunk = chunk2; 682251881Speter break; 683251881Speter } 684253734Speter if (strcmp(answer, "12") == 0) 685253734Speter { 686253734Speter *merged_chunk = apr_array_make(result_pool, 687253734Speter chunk1->nelts + chunk2->nelts, 688253734Speter sizeof(svn_stringbuf_t *)); 689253734Speter apr_array_cat(*merged_chunk, chunk1); 690253734Speter apr_array_cat(*merged_chunk, chunk2); 691253734Speter break; 692253734Speter } 693253734Speter if (strcmp(answer, "21") == 0) 694253734Speter { 695253734Speter *merged_chunk = apr_array_make(result_pool, 696253734Speter chunk1->nelts + chunk2->nelts, 697253734Speter sizeof(svn_stringbuf_t *)); 698253734Speter apr_array_cat(*merged_chunk, chunk2); 699253734Speter apr_array_cat(*merged_chunk, chunk1); 700253734Speter break; 701253734Speter } 702251881Speter else if (strcmp(answer, "p") == 0) 703251881Speter { 704251881Speter *merged_chunk = NULL; 705251881Speter break; 706251881Speter } 707251881Speter else if (strcmp(answer, "e1") == 0) 708251881Speter { 709251881Speter SVN_ERR(edit_chunk(merged_chunk, chunk1, editor_cmd, config, 710251881Speter result_pool, iterpool)); 711251881Speter if (*merged_chunk) 712251881Speter break; 713251881Speter } 714251881Speter else if (strcmp(answer, "e2") == 0) 715251881Speter { 716251881Speter SVN_ERR(edit_chunk(merged_chunk, chunk2, editor_cmd, config, 717251881Speter result_pool, iterpool)); 718251881Speter if (*merged_chunk) 719251881Speter break; 720251881Speter } 721251881Speter else if (strcmp(answer, "eb") == 0) 722251881Speter { 723251881Speter apr_array_header_t *conflict_chunk; 724251881Speter 725251881Speter conflict_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2, 726251881Speter scratch_pool); 727251881Speter SVN_ERR(edit_chunk(merged_chunk, conflict_chunk, editor_cmd, config, 728251881Speter result_pool, iterpool)); 729251881Speter if (*merged_chunk) 730251881Speter break; 731251881Speter } 732251881Speter else if (strcmp(answer, "a") == 0) 733251881Speter { 734251881Speter *abort_merge = TRUE; 735251881Speter break; 736251881Speter } 737251881Speter } 738251881Speter svn_pool_destroy(iterpool); 739251881Speter 740251881Speter return SVN_NO_ERROR; 741251881Speter} 742251881Speter 743251881Speter/* Perform a merge of chunks from FILE1 and FILE2, specified by START1/LEN1 744251881Speter * and START2/LEN2, respectively. Append the result to MERGED_FILE. 745251881Speter * The current line numbers for FILE1 and FILE2 are passed in *CURRENT_LINE1 746251881Speter * and *CURRENT_LINE2, and will be updated to new values upon return. 747251881Speter * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */ 748251881Speterstatic svn_error_t * 749251881Spetermerge_file_chunks(svn_boolean_t *remains_in_conflict, 750251881Speter svn_boolean_t *abort_merge, 751251881Speter apr_file_t *merged_file, 752251881Speter apr_file_t *file1, 753251881Speter apr_file_t *file2, 754251881Speter apr_off_t start1, 755251881Speter apr_off_t len1, 756251881Speter apr_off_t start2, 757251881Speter apr_off_t len2, 758251881Speter svn_linenum_t *current_line1, 759251881Speter svn_linenum_t *current_line2, 760251881Speter const char *editor_cmd, 761251881Speter apr_hash_t *config, 762251881Speter apr_pool_t *scratch_pool) 763251881Speter{ 764251881Speter apr_array_header_t *chunk1; 765251881Speter apr_array_header_t *chunk2; 766251881Speter apr_array_header_t *merged_chunk; 767251881Speter apr_pool_t *iterpool; 768251881Speter int i; 769251881Speter 770251881Speter SVN_ERR(read_diff_chunk(&chunk1, current_line1, file1, *current_line1, 771251881Speter start1, len1, scratch_pool, scratch_pool)); 772251881Speter SVN_ERR(read_diff_chunk(&chunk2, current_line2, file2, *current_line2, 773251881Speter start2, len2, scratch_pool, scratch_pool)); 774251881Speter 775251881Speter SVN_ERR(merge_chunks(&merged_chunk, abort_merge, chunk1, chunk2, 776251881Speter *current_line1, *current_line2, 777251881Speter editor_cmd, config, 778251881Speter scratch_pool, scratch_pool)); 779251881Speter 780251881Speter if (*abort_merge) 781251881Speter return SVN_NO_ERROR; 782251881Speter 783251881Speter /* If the user chose 'postpone' put conflict markers and left/right 784251881Speter * versions into the merged file. */ 785251881Speter if (merged_chunk == NULL) 786251881Speter { 787251881Speter *remains_in_conflict = TRUE; 788251881Speter merged_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2, 789251881Speter scratch_pool); 790251881Speter } 791251881Speter 792251881Speter iterpool = svn_pool_create(scratch_pool); 793251881Speter for (i = 0; i < merged_chunk->nelts; i++) 794251881Speter { 795251881Speter apr_size_t bytes_written; 796251881Speter svn_stringbuf_t *line = APR_ARRAY_IDX(merged_chunk, i, 797251881Speter svn_stringbuf_t *); 798251881Speter 799251881Speter svn_pool_clear(iterpool); 800251881Speter 801251881Speter SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len, 802251881Speter &bytes_written, iterpool)); 803251881Speter if (line->len != bytes_written) 804251881Speter return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, 805251881Speter _("Could not write data to merged file")); 806251881Speter } 807251881Speter svn_pool_destroy(iterpool); 808251881Speter 809251881Speter return SVN_NO_ERROR; 810251881Speter} 811251881Speter 812251881Speter/* Original, modified, and latest all differ from one another. 813251881Speter * This is a conflict and we'll need to ask the user to merge it. */ 814251881Speterstatic svn_error_t * 815251881Speterfile_merge_output_conflict(void *output_baton, 816251881Speter apr_off_t original_start, 817251881Speter apr_off_t original_length, 818251881Speter apr_off_t modified_start, 819251881Speter apr_off_t modified_length, 820251881Speter apr_off_t latest_start, 821251881Speter apr_off_t latest_length, 822251881Speter svn_diff_t *resolved_diff) 823251881Speter{ 824251881Speter struct file_merge_baton *b = output_baton; 825251881Speter 826251881Speter if (b->abort_merge) 827251881Speter return SVN_NO_ERROR; 828251881Speter 829251881Speter SVN_ERR(merge_file_chunks(&b->remains_in_conflict, 830251881Speter &b->abort_merge, 831251881Speter b->merged_file, 832251881Speter b->modified_file, 833251881Speter b->latest_file, 834251881Speter modified_start, 835251881Speter modified_length, 836251881Speter latest_start, 837251881Speter latest_length, 838251881Speter &b->current_line_modified, 839251881Speter &b->current_line_latest, 840251881Speter b->editor_cmd, 841251881Speter b->config, 842251881Speter b->scratch_pool)); 843251881Speter return SVN_NO_ERROR; 844251881Speter} 845251881Speter 846251881Speter/* Our collection of diff output functions that get driven during the merge. */ 847251881Speterstatic svn_diff_output_fns_t file_merge_diff_output_fns = { 848251881Speter file_merge_output_common, 849251881Speter file_merge_output_diff_modified, 850251881Speter file_merge_output_diff_latest, 851251881Speter file_merge_output_diff_common, 852251881Speter file_merge_output_conflict 853251881Speter}; 854251881Speter 855251881Spetersvn_error_t * 856251881Spetersvn_cl__merge_file(const char *base_path, 857251881Speter const char *their_path, 858251881Speter const char *my_path, 859251881Speter const char *merged_path, 860251881Speter const char *wc_path, 861251881Speter const char *path_prefix, 862251881Speter const char *editor_cmd, 863251881Speter apr_hash_t *config, 864251881Speter svn_boolean_t *remains_in_conflict, 865251881Speter apr_pool_t *scratch_pool) 866251881Speter{ 867251881Speter svn_diff_t *diff; 868251881Speter svn_diff_file_options_t *diff_options; 869251881Speter apr_file_t *original_file; 870251881Speter apr_file_t *modified_file; 871251881Speter apr_file_t *latest_file; 872251881Speter apr_file_t *merged_file; 873251881Speter const char *merged_file_name; 874251881Speter struct file_merge_baton fmb; 875251881Speter svn_boolean_t executable; 876251881Speter const char *merged_path_local_style; 877251881Speter const char *merged_rel_path; 878251881Speter const char *wc_path_local_style; 879251881Speter const char *wc_rel_path = svn_dirent_skip_ancestor(path_prefix, wc_path); 880251881Speter 881251881Speter /* PATH_PREFIX may not be an ancestor of WC_PATH, just use the 882251881Speter full WC_PATH in that case. */ 883251881Speter if (wc_rel_path) 884251881Speter wc_path_local_style = svn_dirent_local_style(wc_rel_path, scratch_pool); 885251881Speter else 886251881Speter wc_path_local_style = svn_dirent_local_style(wc_path, scratch_pool); 887251881Speter 888251881Speter SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merging '%s'.\n"), 889251881Speter wc_path_local_style)); 890251881Speter 891251881Speter SVN_ERR(svn_io_file_open(&original_file, base_path, 892251881Speter APR_READ | APR_BUFFERED, 893251881Speter APR_OS_DEFAULT, scratch_pool)); 894251881Speter SVN_ERR(svn_io_file_open(&modified_file, their_path, 895251881Speter APR_READ | APR_BUFFERED, 896251881Speter APR_OS_DEFAULT, scratch_pool)); 897251881Speter SVN_ERR(svn_io_file_open(&latest_file, my_path, 898251881Speter APR_READ | APR_BUFFERED, 899251881Speter APR_OS_DEFAULT, scratch_pool)); 900251881Speter SVN_ERR(svn_io_open_unique_file3(&merged_file, &merged_file_name, 901251881Speter NULL, svn_io_file_del_none, 902251881Speter scratch_pool, scratch_pool)); 903251881Speter 904251881Speter diff_options = svn_diff_file_options_create(scratch_pool); 905251881Speter SVN_ERR(svn_diff_file_diff3_2(&diff, base_path, their_path, my_path, 906251881Speter diff_options, scratch_pool)); 907251881Speter 908251881Speter fmb.original_file = original_file; 909251881Speter fmb.modified_file = modified_file; 910251881Speter fmb.latest_file = latest_file; 911251881Speter fmb.current_line_original = 0; 912251881Speter fmb.current_line_modified = 0; 913251881Speter fmb.current_line_latest = 0; 914251881Speter fmb.merged_file = merged_file; 915251881Speter fmb.remains_in_conflict = FALSE; 916251881Speter fmb.editor_cmd = editor_cmd; 917251881Speter fmb.config = config; 918251881Speter fmb.abort_merge = FALSE; 919251881Speter fmb.scratch_pool = scratch_pool; 920251881Speter 921251881Speter SVN_ERR(svn_diff_output(diff, &fmb, &file_merge_diff_output_fns)); 922251881Speter 923251881Speter SVN_ERR(svn_io_file_close(original_file, scratch_pool)); 924251881Speter SVN_ERR(svn_io_file_close(modified_file, scratch_pool)); 925251881Speter SVN_ERR(svn_io_file_close(latest_file, scratch_pool)); 926251881Speter SVN_ERR(svn_io_file_close(merged_file, scratch_pool)); 927251881Speter 928251881Speter /* Start out assuming that conflicts remain. */ 929251881Speter if (remains_in_conflict) 930251881Speter *remains_in_conflict = TRUE; 931251881Speter 932251881Speter if (fmb.abort_merge) 933251881Speter { 934251881Speter SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool)); 935251881Speter SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merge of '%s' aborted.\n"), 936251881Speter wc_path_local_style)); 937251881Speter return SVN_NO_ERROR; 938251881Speter } 939251881Speter 940251881Speter SVN_ERR(svn_io_is_file_executable(&executable, merged_path, scratch_pool)); 941251881Speter 942251881Speter merged_rel_path = svn_dirent_skip_ancestor(path_prefix, merged_path); 943251881Speter if (merged_rel_path) 944251881Speter merged_path_local_style = svn_dirent_local_style(merged_rel_path, 945251881Speter scratch_pool); 946251881Speter else 947251881Speter merged_path_local_style = svn_dirent_local_style(merged_path, 948251881Speter scratch_pool); 949251881Speter 950251881Speter SVN_ERR_W(svn_io_copy_file(merged_file_name, merged_path, FALSE, 951251881Speter scratch_pool), 952251881Speter apr_psprintf(scratch_pool, 953251881Speter _("Could not write merged result to '%s', saved " 954251881Speter "instead at '%s'.\n'%s' remains in conflict.\n"), 955251881Speter merged_path_local_style, 956251881Speter svn_dirent_local_style(merged_file_name, 957251881Speter scratch_pool), 958251881Speter wc_path_local_style)); 959251881Speter SVN_ERR(svn_io_set_file_executable(merged_path, executable, FALSE, 960251881Speter scratch_pool)); 961251881Speter SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool)); 962251881Speter 963251881Speter /* The merge was not aborted and we could install the merged result. The 964251881Speter * file remains in conflict unless all conflicting sections were resolved. */ 965251881Speter if (remains_in_conflict) 966251881Speter *remains_in_conflict = fmb.remains_in_conflict; 967251881Speter 968251881Speter if (fmb.remains_in_conflict) 969251881Speter SVN_ERR(svn_cmdline_printf( 970251881Speter scratch_pool, 971251881Speter _("Merge of '%s' completed (remains in conflict).\n"), 972251881Speter wc_path_local_style)); 973251881Speter else 974251881Speter SVN_ERR(svn_cmdline_printf( 975251881Speter scratch_pool, _("Merge of '%s' completed.\n"), 976251881Speter wc_path_local_style)); 977251881Speter 978251881Speter return SVN_NO_ERROR; 979251881Speter} 980