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