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