1251881Speter/* 2251881Speter * util.c : routines for doing diffs 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 25251881Speter#include <apr.h> 26251881Speter#include <apr_general.h> 27251881Speter 28251881Speter#include "svn_hash.h" 29251881Speter#include "svn_pools.h" 30251881Speter#include "svn_dirent_uri.h" 31251881Speter#include "svn_props.h" 32251881Speter#include "svn_mergeinfo.h" 33251881Speter#include "svn_error.h" 34251881Speter#include "svn_diff.h" 35251881Speter#include "svn_types.h" 36251881Speter#include "svn_ctype.h" 37251881Speter#include "svn_utf.h" 38251881Speter#include "svn_version.h" 39251881Speter 40251881Speter#include "private/svn_diff_private.h" 41289180Speter#include "private/svn_sorts_private.h" 42251881Speter#include "diff.h" 43251881Speter 44251881Speter#include "svn_private_config.h" 45251881Speter 46251881Speter 47251881Spetersvn_boolean_t 48251881Spetersvn_diff_contains_conflicts(svn_diff_t *diff) 49251881Speter{ 50251881Speter while (diff != NULL) 51251881Speter { 52251881Speter if (diff->type == svn_diff__type_conflict) 53251881Speter { 54251881Speter return TRUE; 55251881Speter } 56251881Speter 57251881Speter diff = diff->next; 58251881Speter } 59251881Speter 60251881Speter return FALSE; 61251881Speter} 62251881Speter 63251881Spetersvn_boolean_t 64251881Spetersvn_diff_contains_diffs(svn_diff_t *diff) 65251881Speter{ 66251881Speter while (diff != NULL) 67251881Speter { 68251881Speter if (diff->type != svn_diff__type_common) 69251881Speter { 70251881Speter return TRUE; 71251881Speter } 72251881Speter 73251881Speter diff = diff->next; 74251881Speter } 75251881Speter 76251881Speter return FALSE; 77251881Speter} 78251881Speter 79251881Spetersvn_error_t * 80289180Spetersvn_diff_output2(svn_diff_t *diff, 81289180Speter void *output_baton, 82289180Speter const svn_diff_output_fns_t *vtable, 83289180Speter svn_cancel_func_t cancel_func, 84289180Speter void *cancel_baton) 85251881Speter{ 86251881Speter svn_error_t *(*output_fn)(void *, 87251881Speter apr_off_t, apr_off_t, 88251881Speter apr_off_t, apr_off_t, 89251881Speter apr_off_t, apr_off_t); 90251881Speter 91251881Speter while (diff != NULL) 92251881Speter { 93289180Speter if (cancel_func) 94289180Speter SVN_ERR(cancel_func(cancel_baton)); 95289180Speter 96251881Speter switch (diff->type) 97251881Speter { 98251881Speter case svn_diff__type_common: 99251881Speter output_fn = vtable->output_common; 100251881Speter break; 101251881Speter 102251881Speter case svn_diff__type_diff_common: 103251881Speter output_fn = vtable->output_diff_common; 104251881Speter break; 105251881Speter 106251881Speter case svn_diff__type_diff_modified: 107251881Speter output_fn = vtable->output_diff_modified; 108251881Speter break; 109251881Speter 110251881Speter case svn_diff__type_diff_latest: 111251881Speter output_fn = vtable->output_diff_latest; 112251881Speter break; 113251881Speter 114251881Speter case svn_diff__type_conflict: 115251881Speter output_fn = NULL; 116251881Speter if (vtable->output_conflict != NULL) 117251881Speter { 118251881Speter SVN_ERR(vtable->output_conflict(output_baton, 119251881Speter diff->original_start, diff->original_length, 120251881Speter diff->modified_start, diff->modified_length, 121251881Speter diff->latest_start, diff->latest_length, 122251881Speter diff->resolved_diff)); 123251881Speter } 124251881Speter break; 125251881Speter 126251881Speter default: 127251881Speter output_fn = NULL; 128251881Speter break; 129251881Speter } 130251881Speter 131251881Speter if (output_fn != NULL) 132251881Speter { 133251881Speter SVN_ERR(output_fn(output_baton, 134251881Speter diff->original_start, diff->original_length, 135251881Speter diff->modified_start, diff->modified_length, 136251881Speter diff->latest_start, diff->latest_length)); 137251881Speter } 138251881Speter 139251881Speter diff = diff->next; 140251881Speter } 141251881Speter 142251881Speter return SVN_NO_ERROR; 143251881Speter} 144251881Speter 145251881Speter 146251881Spetervoid 147251881Spetersvn_diff__normalize_buffer(char **tgt, 148251881Speter apr_off_t *lengthp, 149251881Speter svn_diff__normalize_state_t *statep, 150251881Speter const char *buf, 151251881Speter const svn_diff_file_options_t *opts) 152251881Speter{ 153251881Speter /* Variables for looping through BUF */ 154251881Speter const char *curp, *endp; 155251881Speter 156251881Speter /* Variable to record normalizing state */ 157251881Speter svn_diff__normalize_state_t state = *statep; 158251881Speter 159251881Speter /* Variables to track what needs copying into the target buffer */ 160251881Speter const char *start = buf; 161251881Speter apr_size_t include_len = 0; 162251881Speter svn_boolean_t last_skipped = FALSE; /* makes sure we set 'start' */ 163251881Speter 164251881Speter /* Variable to record the state of the target buffer */ 165251881Speter char *tgt_newend = *tgt; 166251881Speter 167251881Speter /* If this is a noop, then just get out of here. */ 168251881Speter if (! opts->ignore_space && ! opts->ignore_eol_style) 169251881Speter { 170251881Speter *tgt = (char *)buf; 171251881Speter return; 172251881Speter } 173251881Speter 174251881Speter 175251881Speter /* It only took me forever to get this routine right, 176251881Speter so here my thoughts go: 177251881Speter 178251881Speter Below, we loop through the data, doing 2 things: 179251881Speter 180251881Speter - Normalizing 181251881Speter - Copying other data 182251881Speter 183251881Speter The routine tries its hardest *not* to copy data, but instead 184251881Speter returning a pointer into already normalized existing data. 185251881Speter 186251881Speter To this end, a block 'other data' shouldn't be copied when found, 187251881Speter but only as soon as it can't be returned in-place. 188251881Speter 189251881Speter On a character level, there are 3 possible operations: 190251881Speter 191251881Speter - Skip the character (don't include in the normalized data) 192251881Speter - Include the character (do include in the normalizad data) 193251881Speter - Include as another character 194251881Speter This is essentially the same as skipping the current character 195251881Speter and inserting a given character in the output data. 196251881Speter 197251881Speter The macros below (SKIP, INCLUDE and INCLUDE_AS) are defined to 198251881Speter handle the character based operations. The macros themselves 199251881Speter collect character level data into blocks. 200251881Speter 201251881Speter At all times designate the START, INCLUDED_LEN and CURP pointers 202251881Speter an included and and skipped block like this: 203251881Speter 204251881Speter [ start, start + included_len ) [ start + included_len, curp ) 205251881Speter INCLUDED EXCLUDED 206251881Speter 207251881Speter When the routine flips from skipping to including, the last 208251881Speter included block has to be flushed to the output buffer. 209251881Speter */ 210251881Speter 211251881Speter /* Going from including to skipping; only schedules the current 212251881Speter included section for flushing. 213251881Speter Also, simply chop off the character if it's the first in the buffer, 214251881Speter so we can possibly just return the remainder of the buffer */ 215251881Speter#define SKIP \ 216251881Speter do { \ 217251881Speter if (start == curp) \ 218251881Speter ++start; \ 219251881Speter last_skipped = TRUE; \ 220251881Speter } while (0) 221251881Speter 222251881Speter#define INCLUDE \ 223251881Speter do { \ 224251881Speter if (last_skipped) \ 225251881Speter COPY_INCLUDED_SECTION; \ 226251881Speter ++include_len; \ 227251881Speter last_skipped = FALSE; \ 228251881Speter } while (0) 229251881Speter 230251881Speter#define COPY_INCLUDED_SECTION \ 231251881Speter do { \ 232251881Speter if (include_len > 0) \ 233251881Speter { \ 234251881Speter memmove(tgt_newend, start, include_len); \ 235251881Speter tgt_newend += include_len; \ 236251881Speter include_len = 0; \ 237251881Speter } \ 238251881Speter start = curp; \ 239251881Speter } while (0) 240251881Speter 241251881Speter /* Include the current character as character X. 242251881Speter If the current character already *is* X, add it to the 243251881Speter currently included region, increasing chances for consecutive 244251881Speter fully normalized blocks. */ 245251881Speter#define INCLUDE_AS(x) \ 246251881Speter do { \ 247251881Speter if (*curp == (x)) \ 248251881Speter INCLUDE; \ 249251881Speter else \ 250251881Speter { \ 251251881Speter INSERT((x)); \ 252251881Speter SKIP; \ 253251881Speter } \ 254251881Speter } while (0) 255251881Speter 256251881Speter /* Insert character X in the output buffer */ 257251881Speter#define INSERT(x) \ 258251881Speter do { \ 259251881Speter COPY_INCLUDED_SECTION; \ 260251881Speter *tgt_newend++ = (x); \ 261251881Speter } while (0) 262251881Speter 263251881Speter for (curp = buf, endp = buf + *lengthp; curp != endp; ++curp) 264251881Speter { 265251881Speter switch (*curp) 266251881Speter { 267251881Speter case '\r': 268251881Speter if (opts->ignore_eol_style) 269251881Speter INCLUDE_AS('\n'); 270251881Speter else 271251881Speter INCLUDE; 272251881Speter state = svn_diff__normalize_state_cr; 273251881Speter break; 274251881Speter 275251881Speter case '\n': 276251881Speter if (state == svn_diff__normalize_state_cr 277251881Speter && opts->ignore_eol_style) 278251881Speter SKIP; 279251881Speter else 280251881Speter INCLUDE; 281251881Speter state = svn_diff__normalize_state_normal; 282251881Speter break; 283251881Speter 284251881Speter default: 285251881Speter if (svn_ctype_isspace(*curp) 286251881Speter && opts->ignore_space != svn_diff_file_ignore_space_none) 287251881Speter { 288251881Speter /* Whitespace but not '\r' or '\n' */ 289251881Speter if (state != svn_diff__normalize_state_whitespace 290251881Speter && opts->ignore_space 291251881Speter == svn_diff_file_ignore_space_change) 292251881Speter /*### If we can postpone insertion of the space 293251881Speter until the next non-whitespace character, 294251881Speter we have a potential of reducing the number of copies: 295251881Speter If this space is followed by more spaces, 296251881Speter this will cause a block-copy. 297251881Speter If the next non-space block is considered normalized 298251881Speter *and* preceded by a space, we can take advantage of that. */ 299251881Speter /* Note, the above optimization applies to 90% of the source 300251881Speter lines in our own code, since it (generally) doesn't use 301251881Speter more than one space per blank section, except for the 302251881Speter beginning of a line. */ 303251881Speter INCLUDE_AS(' '); 304251881Speter else 305251881Speter SKIP; 306251881Speter state = svn_diff__normalize_state_whitespace; 307251881Speter } 308251881Speter else 309251881Speter { 310251881Speter /* Non-whitespace character, or whitespace character in 311251881Speter svn_diff_file_ignore_space_none mode. */ 312251881Speter INCLUDE; 313251881Speter state = svn_diff__normalize_state_normal; 314251881Speter } 315251881Speter } 316251881Speter } 317251881Speter 318251881Speter /* If we're not in whitespace, flush the last chunk of data. 319251881Speter * Note that this will work correctly when this is the last chunk of the 320251881Speter * file: 321251881Speter * * If there is an eol, it will either have been output when we entered 322251881Speter * the state_cr, or it will be output now. 323251881Speter * * If there is no eol and we're not in whitespace, then we just output 324251881Speter * everything below. 325251881Speter * * If there's no eol and we are in whitespace, we want to ignore 326251881Speter * whitespace unconditionally. */ 327251881Speter 328251881Speter if (*tgt == tgt_newend) 329251881Speter { 330251881Speter /* we haven't copied any data in to *tgt and our chunk consists 331251881Speter only of one block of (already normalized) data. 332251881Speter Just return the block. */ 333251881Speter *tgt = (char *)start; 334251881Speter *lengthp = include_len; 335251881Speter } 336251881Speter else 337251881Speter { 338251881Speter COPY_INCLUDED_SECTION; 339251881Speter *lengthp = tgt_newend - *tgt; 340251881Speter } 341251881Speter 342251881Speter *statep = state; 343251881Speter 344251881Speter#undef SKIP 345251881Speter#undef INCLUDE 346251881Speter#undef INCLUDE_AS 347251881Speter#undef INSERT 348251881Speter#undef COPY_INCLUDED_SECTION 349251881Speter} 350251881Speter 351251881Spetersvn_error_t * 352251881Spetersvn_diff__unified_append_no_newline_msg(svn_stringbuf_t *stringbuf, 353251881Speter const char *header_encoding, 354251881Speter apr_pool_t *scratch_pool) 355251881Speter{ 356251881Speter const char *out_str; 357251881Speter 358251881Speter SVN_ERR(svn_utf_cstring_from_utf8_ex2( 359251881Speter &out_str, 360251881Speter APR_EOL_STR 361251881Speter SVN_DIFF__NO_NEWLINE_AT_END_OF_FILE APR_EOL_STR, 362251881Speter header_encoding, scratch_pool)); 363251881Speter svn_stringbuf_appendcstr(stringbuf, out_str); 364251881Speter return SVN_NO_ERROR; 365251881Speter} 366251881Speter 367251881Spetersvn_error_t * 368251881Spetersvn_diff__unified_write_hunk_header(svn_stream_t *output_stream, 369251881Speter const char *header_encoding, 370251881Speter const char *hunk_delimiter, 371251881Speter apr_off_t old_start, 372251881Speter apr_off_t old_length, 373251881Speter apr_off_t new_start, 374251881Speter apr_off_t new_length, 375251881Speter const char *hunk_extra_context, 376251881Speter apr_pool_t *scratch_pool) 377251881Speter{ 378251881Speter SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, 379251881Speter scratch_pool, 380251881Speter "%s -%" APR_OFF_T_FMT, 381251881Speter hunk_delimiter, old_start)); 382251881Speter /* If the hunk length is 1, suppress the number of lines in the hunk 383251881Speter * (it is 1 implicitly) */ 384251881Speter if (old_length != 1) 385251881Speter { 386251881Speter SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, 387251881Speter scratch_pool, 388251881Speter ",%" APR_OFF_T_FMT, old_length)); 389251881Speter } 390251881Speter 391251881Speter SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, 392251881Speter scratch_pool, 393251881Speter " +%" APR_OFF_T_FMT, new_start)); 394251881Speter if (new_length != 1) 395251881Speter { 396251881Speter SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, 397251881Speter scratch_pool, 398251881Speter ",%" APR_OFF_T_FMT, new_length)); 399251881Speter } 400251881Speter 401251881Speter if (hunk_extra_context == NULL) 402251881Speter hunk_extra_context = ""; 403251881Speter SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, 404251881Speter scratch_pool, 405251881Speter " %s%s%s" APR_EOL_STR, 406251881Speter hunk_delimiter, 407251881Speter hunk_extra_context[0] ? " " : "", 408251881Speter hunk_extra_context)); 409251881Speter return SVN_NO_ERROR; 410251881Speter} 411251881Speter 412251881Spetersvn_error_t * 413251881Spetersvn_diff__unidiff_write_header(svn_stream_t *output_stream, 414251881Speter const char *header_encoding, 415251881Speter const char *old_header, 416251881Speter const char *new_header, 417251881Speter apr_pool_t *scratch_pool) 418251881Speter{ 419251881Speter SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, 420251881Speter scratch_pool, 421251881Speter "--- %s" APR_EOL_STR 422251881Speter "+++ %s" APR_EOL_STR, 423251881Speter old_header, 424251881Speter new_header)); 425251881Speter return SVN_NO_ERROR; 426251881Speter} 427251881Speter 428251881Speter/* A helper function for display_prop_diffs. Output the differences between 429251881Speter the mergeinfo stored in ORIG_MERGEINFO_VAL and NEW_MERGEINFO_VAL in a 430251881Speter human-readable form to OUTSTREAM, using ENCODING. Use POOL for temporary 431251881Speter allocations. */ 432251881Speterstatic svn_error_t * 433251881Speterdisplay_mergeinfo_diff(const char *old_mergeinfo_val, 434251881Speter const char *new_mergeinfo_val, 435251881Speter const char *encoding, 436251881Speter svn_stream_t *outstream, 437251881Speter apr_pool_t *pool) 438251881Speter{ 439251881Speter apr_hash_t *old_mergeinfo_hash, *new_mergeinfo_hash, *added, *deleted; 440251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 441251881Speter apr_hash_index_t *hi; 442251881Speter 443251881Speter if (old_mergeinfo_val) 444251881Speter SVN_ERR(svn_mergeinfo_parse(&old_mergeinfo_hash, old_mergeinfo_val, pool)); 445251881Speter else 446251881Speter old_mergeinfo_hash = NULL; 447251881Speter 448251881Speter if (new_mergeinfo_val) 449251881Speter SVN_ERR(svn_mergeinfo_parse(&new_mergeinfo_hash, new_mergeinfo_val, pool)); 450251881Speter else 451251881Speter new_mergeinfo_hash = NULL; 452251881Speter 453251881Speter SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, old_mergeinfo_hash, 454251881Speter new_mergeinfo_hash, 455251881Speter TRUE, pool, pool)); 456251881Speter 457289180Speter /* Print a hint for 'svn patch' or smilar tools, indicating the 458289180Speter * number of reverse-merges and forward-merges. */ 459289180Speter SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool, 460289180Speter "## -0,%u +0,%u ##%s", 461289180Speter apr_hash_count(deleted), 462289180Speter apr_hash_count(added), 463289180Speter APR_EOL_STR)); 464289180Speter 465251881Speter for (hi = apr_hash_first(pool, deleted); 466251881Speter hi; hi = apr_hash_next(hi)) 467251881Speter { 468289180Speter const char *from_path = apr_hash_this_key(hi); 469289180Speter svn_rangelist_t *merge_revarray = apr_hash_this_val(hi); 470251881Speter svn_string_t *merge_revstr; 471251881Speter 472251881Speter svn_pool_clear(iterpool); 473251881Speter SVN_ERR(svn_rangelist_to_string(&merge_revstr, merge_revarray, 474251881Speter iterpool)); 475251881Speter 476251881Speter SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, iterpool, 477251881Speter _(" Reverse-merged %s:r%s%s"), 478251881Speter from_path, merge_revstr->data, 479251881Speter APR_EOL_STR)); 480251881Speter } 481251881Speter 482251881Speter for (hi = apr_hash_first(pool, added); 483251881Speter hi; hi = apr_hash_next(hi)) 484251881Speter { 485289180Speter const char *from_path = apr_hash_this_key(hi); 486289180Speter svn_rangelist_t *merge_revarray = apr_hash_this_val(hi); 487251881Speter svn_string_t *merge_revstr; 488251881Speter 489251881Speter svn_pool_clear(iterpool); 490251881Speter SVN_ERR(svn_rangelist_to_string(&merge_revstr, merge_revarray, 491251881Speter iterpool)); 492251881Speter 493251881Speter SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, iterpool, 494251881Speter _(" Merged %s:r%s%s"), 495251881Speter from_path, merge_revstr->data, 496251881Speter APR_EOL_STR)); 497251881Speter } 498251881Speter 499251881Speter svn_pool_destroy(iterpool); 500251881Speter return SVN_NO_ERROR; 501251881Speter} 502251881Speter 503289180Speter/* svn_sort__array callback handling svn_prop_t by name */ 504286506Speterstatic int 505286506Speterpropchange_sort(const void *k1, const void *k2) 506286506Speter{ 507286506Speter const svn_prop_t *propchange1 = k1; 508286506Speter const svn_prop_t *propchange2 = k2; 509286506Speter 510286506Speter return strcmp(propchange1->name, propchange2->name); 511286506Speter} 512286506Speter 513251881Spetersvn_error_t * 514251881Spetersvn_diff__display_prop_diffs(svn_stream_t *outstream, 515251881Speter const char *encoding, 516251881Speter const apr_array_header_t *propchanges, 517251881Speter apr_hash_t *original_props, 518251881Speter svn_boolean_t pretty_print_mergeinfo, 519289180Speter int context_size, 520289180Speter svn_cancel_func_t cancel_func, 521289180Speter void *cancel_baton, 522286506Speter apr_pool_t *scratch_pool) 523251881Speter{ 524286506Speter apr_pool_t *pool = scratch_pool; 525251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 526286506Speter apr_array_header_t *changes = apr_array_copy(scratch_pool, propchanges); 527251881Speter int i; 528251881Speter 529289180Speter svn_sort__array(changes, propchange_sort); 530286506Speter 531286506Speter for (i = 0; i < changes->nelts; i++) 532251881Speter { 533251881Speter const char *action; 534251881Speter const svn_string_t *original_value; 535251881Speter const svn_prop_t *propchange 536286506Speter = &APR_ARRAY_IDX(changes, i, svn_prop_t); 537251881Speter 538251881Speter if (original_props) 539251881Speter original_value = svn_hash_gets(original_props, propchange->name); 540251881Speter else 541251881Speter original_value = NULL; 542251881Speter 543251881Speter /* If the property doesn't exist on either side, or if it exists 544251881Speter with the same value, skip it. This can happen if the client is 545251881Speter hitting an old mod_dav_svn server that doesn't understand the 546251881Speter "send-all" REPORT style. */ 547251881Speter if ((! (original_value || propchange->value)) 548251881Speter || (original_value && propchange->value 549251881Speter && svn_string_compare(original_value, propchange->value))) 550251881Speter continue; 551251881Speter 552251881Speter svn_pool_clear(iterpool); 553251881Speter 554251881Speter if (! original_value) 555251881Speter action = "Added"; 556251881Speter else if (! propchange->value) 557251881Speter action = "Deleted"; 558251881Speter else 559251881Speter action = "Modified"; 560251881Speter SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, iterpool, 561251881Speter "%s: %s%s", action, 562251881Speter propchange->name, APR_EOL_STR)); 563251881Speter 564251881Speter if (pretty_print_mergeinfo 565251881Speter && strcmp(propchange->name, SVN_PROP_MERGEINFO) == 0) 566251881Speter { 567251881Speter const char *orig = original_value ? original_value->data : NULL; 568251881Speter const char *val = propchange->value ? propchange->value->data : NULL; 569251881Speter svn_error_t *err = display_mergeinfo_diff(orig, val, encoding, 570251881Speter outstream, iterpool); 571251881Speter 572251881Speter /* Issue #3896: If we can't pretty-print mergeinfo differences 573251881Speter because invalid mergeinfo is present, then don't let the diff 574251881Speter fail, just print the diff as any other property. */ 575251881Speter if (err && err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 576251881Speter { 577251881Speter svn_error_clear(err); 578251881Speter } 579251881Speter else 580251881Speter { 581251881Speter SVN_ERR(err); 582251881Speter continue; 583251881Speter } 584251881Speter } 585251881Speter 586251881Speter { 587251881Speter svn_diff_t *diff; 588251881Speter svn_diff_file_options_t options = { 0 }; 589251881Speter const svn_string_t *orig 590251881Speter = original_value ? original_value 591251881Speter : svn_string_create_empty(iterpool); 592251881Speter const svn_string_t *val 593251881Speter = propchange->value ? propchange->value 594251881Speter : svn_string_create_empty(iterpool); 595251881Speter 596251881Speter SVN_ERR(svn_diff_mem_string_diff(&diff, orig, val, &options, 597251881Speter iterpool)); 598251881Speter 599251881Speter /* UNIX patch will try to apply a diff even if the diff header 600251881Speter * is missing. It tries to be helpful by asking the user for a 601251881Speter * target filename when it can't determine the target filename 602251881Speter * from the diff header. But there usually are no files which 603251881Speter * UNIX patch could apply the property diff to, so we use "##" 604251881Speter * instead of "@@" as the default hunk delimiter for property diffs. 605289180Speter * We also suppress the diff header. */ 606289180Speter SVN_ERR(svn_diff_mem_string_output_unified3( 607251881Speter outstream, diff, FALSE /* no header */, "##", NULL, NULL, 608289180Speter encoding, orig, val, context_size, 609289180Speter cancel_func, cancel_baton, iterpool)); 610251881Speter } 611251881Speter } 612251881Speter svn_pool_destroy(iterpool); 613251881Speter 614251881Speter return SVN_NO_ERROR; 615251881Speter} 616251881Speter 617251881Speter 618251881Speter/* Return the library version number. */ 619251881Speterconst svn_version_t * 620251881Spetersvn_diff_version(void) 621251881Speter{ 622251881Speter SVN_VERSION_BODY; 623251881Speter} 624