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