1/*
2 * diff_local.c: comparing local trees with each other
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* ==================================================================== */
25
26
27
28/*** Includes. ***/
29
30#include <apr_strings.h>
31#include <apr_pools.h>
32#include <apr_hash.h>
33#include "svn_hash.h"
34#include "svn_types.h"
35#include "svn_wc.h"
36#include "svn_diff.h"
37#include "svn_client.h"
38#include "svn_string.h"
39#include "svn_error.h"
40#include "svn_dirent_uri.h"
41#include "svn_io.h"
42#include "svn_pools.h"
43#include "svn_props.h"
44#include "svn_sorts.h"
45#include "svn_subst.h"
46#include "client.h"
47
48#include "private/svn_sorts_private.h"
49#include "private/svn_wc_private.h"
50#include "private/svn_diff_tree.h"
51
52#include "svn_private_config.h"
53
54
55/* Try to get properties for LOCAL_ABSPATH and return them in the property
56 * hash *PROPS. If there are no properties because LOCAL_ABSPATH is not
57 * versioned, return an empty property hash. */
58static svn_error_t *
59get_props(apr_hash_t **props,
60          const char *local_abspath,
61          svn_wc_context_t *wc_ctx,
62          apr_pool_t *result_pool,
63          apr_pool_t *scratch_pool)
64{
65  svn_error_t *err;
66
67  err = svn_wc_prop_list2(props, wc_ctx, local_abspath, result_pool,
68                          scratch_pool);
69  if (err)
70    {
71      if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
72          err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
73        {
74          svn_error_clear(err);
75          *props = apr_hash_make(result_pool);
76
77          /* ### Apply autoprops, like 'svn add' would? */
78        }
79      else
80        return svn_error_trace(err);
81    }
82
83  return SVN_NO_ERROR;
84}
85
86/* Forward declaration */
87static svn_error_t *
88do_file_diff(const char *left_abspath,
89             const char *right_abspath,
90             const char *left_root_abspath,
91             const char *right_root_abspath,
92             svn_boolean_t left_only,
93             svn_boolean_t right_only,
94             void *parent_baton,
95             const svn_diff_tree_processor_t *diff_processor,
96             svn_client_ctx_t *ctx,
97             apr_pool_t *scratch_pool);
98
99/* Forward declaration */
100static svn_error_t *
101do_dir_diff(const char *left_abspath,
102            const char *right_abspath,
103            const char *left_root_abspath,
104            const char *right_root_abspath,
105            svn_boolean_t left_only,
106            svn_boolean_t right_only,
107            svn_boolean_t left_before_right,
108            svn_depth_t depth,
109            void *parent_baton,
110            const svn_diff_tree_processor_t *diff_processor,
111            svn_client_ctx_t *ctx,
112            apr_pool_t *scratch_pool);
113
114/* Produce a diff of depth DEPTH between two arbitrary directories at
115 * LEFT_ABSPATH1 and RIGHT_ABSPATH2, using the provided diff callbacks
116 * to show file changes and, for versioned nodes, property changes.
117 *
118 * Report paths as relative from LEFT_ROOT_ABSPATH/RIGHT_ROOT_ABSPATH.
119 *
120 * If LEFT_ONLY is TRUE, only the left source exists (= everything will
121 * be reported as deleted). If RIGHT_ONLY is TRUE, only the right source
122 * exists (= everything will be reported as added).
123 *
124 * If LEFT_BEFORE_RIGHT is TRUE and left and right are unrelated, left is
125 * reported first. If false, right is reported first. (This is to allow
126 * producing a proper inverse diff).
127 *
128 * Walk the sources according to depth, and report with parent baton
129 * PARENT_BATON. */
130static svn_error_t *
131inner_dir_diff(const char *left_abspath,
132               const char *right_abspath,
133               const char *left_root_abspath,
134               const char *right_root_abspath,
135               svn_boolean_t left_only,
136               svn_boolean_t right_only,
137               svn_boolean_t left_before_right,
138               svn_depth_t depth,
139               void *parent_baton,
140               const svn_diff_tree_processor_t *diff_processor,
141               svn_client_ctx_t *ctx,
142               apr_pool_t *scratch_pool)
143{
144  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
145  apr_hash_t *left_dirents;
146  apr_hash_t *right_dirents;
147  apr_array_header_t *sorted_dirents;
148  svn_error_t *err;
149  svn_depth_t depth_below_here;
150  int i;
151
152  SVN_ERR_ASSERT(depth >= svn_depth_files && depth <= svn_depth_infinity);
153
154  if (!right_only)
155    {
156      err = svn_io_get_dirents3(&left_dirents, left_abspath, FALSE,
157                                scratch_pool, iterpool);
158
159      if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
160                  || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
161        {
162          svn_error_clear(err);
163          left_dirents = apr_hash_make(scratch_pool);
164          right_only = TRUE;
165        }
166      else
167        SVN_ERR(err);
168    }
169  else
170    left_dirents = apr_hash_make(scratch_pool);
171
172  if (!left_only)
173    {
174      err = svn_io_get_dirents3(&right_dirents, right_abspath, FALSE,
175                                scratch_pool, iterpool);
176
177      if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
178                  || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
179        {
180          svn_error_clear(err);
181          right_dirents = apr_hash_make(scratch_pool);
182          left_only = TRUE;
183        }
184      else
185        SVN_ERR(err);
186    }
187  else
188    right_dirents = apr_hash_make(scratch_pool);
189
190  if (left_only && right_only)
191    return SVN_NO_ERROR; /* Somebody deleted the directory?? */
192
193  if (depth != svn_depth_infinity)
194    depth_below_here = svn_depth_empty;
195  else
196    depth_below_here = svn_depth_infinity;
197
198  sorted_dirents = svn_sort__hash(apr_hash_merge(iterpool, left_dirents,
199                                                 right_dirents, NULL, NULL),
200                                  svn_sort_compare_items_as_paths,
201                                  scratch_pool);
202
203  for (i = 0; i < sorted_dirents->nelts; i++)
204    {
205      svn_sort__item_t* elt = &APR_ARRAY_IDX(sorted_dirents, i, svn_sort__item_t);
206      svn_io_dirent2_t *left_dirent;
207      svn_io_dirent2_t *right_dirent;
208      const char *child_left_abspath;
209      const char *child_right_abspath;
210
211      svn_pool_clear(iterpool);
212
213      if (ctx->cancel_func)
214        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
215
216      if (svn_wc_is_adm_dir(elt->key, iterpool))
217        continue;
218
219      left_dirent = right_only ? NULL : svn_hash_gets(left_dirents, elt->key);
220      right_dirent = left_only ? NULL : svn_hash_gets(right_dirents, elt->key);
221
222      child_left_abspath = svn_dirent_join(left_abspath, elt->key, iterpool);
223      child_right_abspath = svn_dirent_join(right_abspath, elt->key, iterpool);
224
225      if (((left_dirent == NULL) != (right_dirent == NULL))
226          || (left_dirent->kind != right_dirent->kind))
227        {
228          /* Report delete and/or add */
229          if (left_dirent && left_before_right)
230            {
231              if (left_dirent->kind == svn_node_file)
232                SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath,
233                                     left_root_abspath, right_root_abspath,
234                                     TRUE, FALSE, parent_baton,
235                                     diff_processor, ctx, iterpool));
236              else if (depth >= svn_depth_immediates)
237                SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath,
238                                    left_root_abspath, right_root_abspath,
239                                    TRUE, FALSE, left_before_right,
240                                    depth_below_here, parent_baton,
241                                    diff_processor, ctx, iterpool));
242            }
243
244          if (right_dirent)
245            {
246              if (right_dirent->kind == svn_node_file)
247                SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath,
248                                     left_root_abspath, right_root_abspath,
249                                     FALSE, TRUE, parent_baton,
250                                     diff_processor, ctx, iterpool));
251              else if (depth >= svn_depth_immediates)
252                SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath,
253                                    left_root_abspath, right_root_abspath,
254                                    FALSE, TRUE,  left_before_right,
255                                    depth_below_here, parent_baton,
256                                    diff_processor, ctx, iterpool));
257            }
258
259          if (left_dirent && !left_before_right)
260            {
261              if (left_dirent->kind == svn_node_file)
262                SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath,
263                                     left_root_abspath, right_root_abspath,
264                                     TRUE, FALSE, parent_baton,
265                                     diff_processor, ctx, iterpool));
266              else if (depth >= svn_depth_immediates)
267                SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath,
268                                    left_root_abspath, right_root_abspath,
269                                    TRUE, FALSE,  left_before_right,
270                                    depth_below_here, parent_baton,
271                                    diff_processor, ctx, iterpool));
272            }
273        }
274      else if (left_dirent->kind == svn_node_file)
275        {
276          /* Perform file-file diff */
277          SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath,
278                               left_root_abspath, right_root_abspath,
279                               FALSE, FALSE, parent_baton,
280                               diff_processor, ctx, iterpool));
281        }
282      else if (depth >= svn_depth_immediates)
283        {
284          /* Perform dir-dir diff */
285          SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath,
286                              left_root_abspath, right_root_abspath,
287                              FALSE, FALSE,  left_before_right,
288                              depth_below_here, parent_baton,
289                              diff_processor, ctx, iterpool));
290        }
291    }
292
293  return SVN_NO_ERROR;
294}
295
296/* Translates *LEFT_ABSPATH to a temporary file if PROPS specify that the
297   file needs translation. *LEFT_ABSPATH is updated to point to a file that
298   lives at least as long as RESULT_POOL when translation is necessary.
299   Otherwise the value is not updated */
300static svn_error_t *
301translate_if_necessary(const char **local_abspath,
302                       apr_hash_t *props,
303                       svn_cancel_func_t cancel_func,
304                       void *cancel_baton,
305                       apr_pool_t *result_pool,
306                       apr_pool_t *scratch_pool)
307{
308  const svn_string_t *eol_style_val;
309  const svn_string_t *keywords_val;
310  svn_subst_eol_style_t eol_style;
311  const char *eol;
312  apr_hash_t *keywords;
313  svn_stream_t *contents;
314  svn_stream_t *dst;
315
316  /* if (svn_hash_gets(props, SVN_PROP_SPECIAL))
317      ### TODO: Implement */
318
319  eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
320  keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
321
322  if (eol_style_val)
323    svn_subst_eol_style_from_value(&eol_style, &eol, eol_style_val->data);
324  else
325    {
326      eol = NULL;
327      eol_style = svn_subst_eol_style_none;
328    }
329
330  if (keywords_val)
331    SVN_ERR(svn_subst_build_keywords3(&keywords, keywords_val->data,
332                                      APR_STRINGIFY(SVN_INVALID_REVNUM),
333                                      "", "", 0, "", scratch_pool));
334  else
335    keywords = NULL;
336
337  if (!svn_subst_translation_required(eol_style, eol, keywords, FALSE, FALSE))
338    return SVN_NO_ERROR;
339
340  SVN_ERR(svn_stream_open_readonly(&contents, *local_abspath,
341                                    scratch_pool, scratch_pool));
342
343  SVN_ERR(svn_stream_open_unique(&dst, local_abspath, NULL,
344                                  svn_io_file_del_on_pool_cleanup,
345                                  result_pool, scratch_pool));
346
347  dst = svn_subst_stream_translated(dst, eol, TRUE /* repair */,
348                                    keywords, FALSE /* expand */,
349                                    scratch_pool);
350
351  SVN_ERR(svn_stream_copy3(contents, dst, cancel_func, cancel_baton,
352                           scratch_pool));
353
354  return SVN_NO_ERROR;
355}
356
357/* Handles reporting of a file for inner_dir_diff */
358static svn_error_t *
359do_file_diff(const char *left_abspath,
360             const char *right_abspath,
361             const char *left_root_abspath,
362             const char *right_root_abspath,
363             svn_boolean_t left_only,
364             svn_boolean_t right_only,
365             void *parent_baton,
366             const svn_diff_tree_processor_t *diff_processor,
367             svn_client_ctx_t *ctx,
368             apr_pool_t *scratch_pool)
369{
370  const char *relpath;
371  svn_diff_source_t *left_source;
372  svn_diff_source_t *right_source;
373  svn_boolean_t skip = FALSE;
374  apr_hash_t *left_props;
375  apr_hash_t *right_props;
376  void *file_baton;
377
378  relpath = svn_dirent_skip_ancestor(left_root_abspath, left_abspath);
379
380 if (! right_only)
381    left_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
382  else
383    left_source = NULL;
384
385  if (! left_only)
386    right_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
387  else
388    right_source = NULL;
389
390  SVN_ERR(diff_processor->file_opened(&file_baton, &skip,
391                                      relpath,
392                                      left_source,
393                                      right_source,
394                                      NULL /* copyfrom_source */,
395                                      parent_baton,
396                                      diff_processor,
397                                      scratch_pool,
398                                      scratch_pool));
399
400  if (skip)
401    return SVN_NO_ERROR;
402
403   if (! right_only)
404    {
405      SVN_ERR(get_props(&left_props, left_abspath, ctx->wc_ctx,
406                        scratch_pool, scratch_pool));
407
408      /* We perform a mimetype detection to avoid diffing binary files
409         for textual changes.*/
410      if (! svn_hash_gets(left_props, SVN_PROP_MIME_TYPE))
411        {
412          const char *mime_type;
413
414          /* ### Use libmagic magic? */
415          SVN_ERR(svn_io_detect_mimetype2(&mime_type, left_abspath,
416                                          ctx->mimetypes_map, scratch_pool));
417
418          if (mime_type)
419            svn_hash_sets(left_props, SVN_PROP_MIME_TYPE,
420                          svn_string_create(mime_type, scratch_pool));
421        }
422
423      SVN_ERR(translate_if_necessary(&left_abspath, left_props,
424                                     ctx->cancel_func, ctx->cancel_baton,
425                                     scratch_pool, scratch_pool));
426    }
427  else
428    left_props = NULL;
429
430  if (! left_only)
431    {
432      SVN_ERR(get_props(&right_props, right_abspath, ctx->wc_ctx,
433                        scratch_pool, scratch_pool));
434
435      /* We perform a mimetype detection to avoid diffing binary files
436         for textual changes.*/
437      if (! svn_hash_gets(right_props, SVN_PROP_MIME_TYPE))
438        {
439          const char *mime_type;
440
441          /* ### Use libmagic magic? */
442          SVN_ERR(svn_io_detect_mimetype2(&mime_type, right_abspath,
443                                          ctx->mimetypes_map, scratch_pool));
444
445          if (mime_type)
446            svn_hash_sets(right_props, SVN_PROP_MIME_TYPE,
447                          svn_string_create(mime_type, scratch_pool));
448        }
449
450      SVN_ERR(translate_if_necessary(&right_abspath, right_props,
451                                     ctx->cancel_func, ctx->cancel_baton,
452                                     scratch_pool, scratch_pool));
453
454    }
455  else
456    right_props = NULL;
457
458  if (left_only)
459    {
460      SVN_ERR(diff_processor->file_deleted(relpath,
461                                           left_source,
462                                           left_abspath,
463                                           left_props,
464                                           file_baton,
465                                           diff_processor,
466                                           scratch_pool));
467    }
468  else if (right_only)
469    {
470      SVN_ERR(diff_processor->file_added(relpath,
471                                         NULL /* copyfrom_source */,
472                                         right_source,
473                                         NULL /* copyfrom_file */,
474                                         right_abspath,
475                                         NULL /* copyfrom_props */,
476                                         right_props,
477                                         file_baton,
478                                         diff_processor,
479                                         scratch_pool));
480    }
481  else
482    {
483      /* ### Perform diff -> close/changed */
484      svn_boolean_t same;
485      apr_array_header_t *prop_changes;
486
487      SVN_ERR(svn_io_files_contents_same_p(&same, left_abspath, right_abspath,
488                                           scratch_pool));
489
490      SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props,
491                             scratch_pool));
492
493      if (!same || prop_changes->nelts > 0)
494        {
495          SVN_ERR(diff_processor->file_changed(relpath,
496                                               left_source,
497                                               right_source,
498                                               same ? NULL : left_abspath,
499                                               same ? NULL : right_abspath,
500                                               left_props,
501                                               right_props,
502                                               !same,
503                                               prop_changes,
504                                               file_baton,
505                                               diff_processor,
506                                               scratch_pool));
507        }
508      else
509        {
510          SVN_ERR(diff_processor->file_closed(relpath,
511                                            left_source,
512                                            right_source,
513                                            file_baton,
514                                            diff_processor,
515                                            scratch_pool));
516        }
517    }
518  return SVN_NO_ERROR;
519}
520
521
522/* Handles reporting of a directory and its children for inner_dir_diff */
523static svn_error_t *
524do_dir_diff(const char *left_abspath,
525            const char *right_abspath,
526            const char *left_root_abspath,
527            const char *right_root_abspath,
528            svn_boolean_t left_only,
529            svn_boolean_t right_only,
530            svn_boolean_t left_before_right,
531            svn_depth_t depth,
532            void *parent_baton,
533            const svn_diff_tree_processor_t *diff_processor,
534            svn_client_ctx_t *ctx,
535            apr_pool_t *scratch_pool)
536{
537  const char *relpath;
538  svn_diff_source_t *left_source;
539  svn_diff_source_t *right_source;
540  svn_boolean_t skip = FALSE;
541  svn_boolean_t skip_children = FALSE;
542  void *dir_baton;
543  apr_hash_t *left_props;
544  apr_hash_t *right_props;
545
546  relpath = svn_dirent_skip_ancestor(left_root_abspath, left_abspath);
547
548  if (! right_only)
549    {
550      left_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
551      SVN_ERR(get_props(&left_props, left_abspath, ctx->wc_ctx,
552                        scratch_pool, scratch_pool));
553    }
554  else
555    {
556      left_source = NULL;
557      left_props = NULL;
558    }
559
560  if (! left_only)
561    {
562      right_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
563      SVN_ERR(get_props(&right_props, right_abspath, ctx->wc_ctx,
564                        scratch_pool, scratch_pool));
565    }
566  else
567    {
568      right_source = NULL;
569      right_props = NULL;
570    }
571
572  SVN_ERR(diff_processor->dir_opened(&dir_baton, &skip, &skip_children,
573                                     relpath,
574                                     left_source,
575                                     right_source,
576                                     NULL /* copyfrom_source */,
577                                     parent_baton,
578                                     diff_processor,
579                                     scratch_pool, scratch_pool));
580
581  if (!skip_children)
582    {
583      if (depth >= svn_depth_files)
584        SVN_ERR(inner_dir_diff(left_abspath, right_abspath,
585                               left_root_abspath, right_root_abspath,
586                               left_only, right_only,
587                               left_before_right, depth,
588                               dir_baton,
589                               diff_processor, ctx, scratch_pool));
590    }
591  else if (skip)
592    return SVN_NO_ERROR;
593
594  if (left_props && right_props)
595    {
596      apr_array_header_t *prop_diffs;
597
598      SVN_ERR(svn_prop_diffs(&prop_diffs, right_props, left_props,
599                             scratch_pool));
600
601      if (prop_diffs->nelts)
602        {
603          SVN_ERR(diff_processor->dir_changed(relpath,
604                                              left_source,
605                                              right_source,
606                                              left_props,
607                                              right_props,
608                                              prop_diffs,
609                                              dir_baton,
610                                              diff_processor,
611                                              scratch_pool));
612          return SVN_NO_ERROR;
613        }
614    }
615
616  if (left_source && right_source)
617    {
618      SVN_ERR(diff_processor->dir_closed(relpath,
619                                         left_source,
620                                         right_source,
621                                         dir_baton,
622                                         diff_processor,
623                                         scratch_pool));
624    }
625  else if (left_source)
626    {
627      SVN_ERR(diff_processor->dir_deleted(relpath,
628                                          left_source,
629                                          left_props,
630                                          dir_baton,
631                                          diff_processor,
632                                          scratch_pool));
633    }
634  else
635    {
636      SVN_ERR(diff_processor->dir_added(relpath,
637                                        NULL /* copyfrom_source */,
638                                        right_source,
639                                        NULL /* copyfrom_props */,
640                                        right_props,
641                                        dir_baton,
642                                        diff_processor,
643                                        scratch_pool));
644    }
645
646  return SVN_NO_ERROR;
647}
648
649svn_error_t *
650svn_client__arbitrary_nodes_diff(const char *left_abspath,
651                                 const char *right_abspath,
652                                 svn_depth_t depth,
653                                 const svn_diff_tree_processor_t *diff_processor,
654                                 svn_client_ctx_t *ctx,
655                                 apr_pool_t *scratch_pool)
656{
657  svn_node_kind_t left_kind;
658  svn_node_kind_t right_kind;
659  const char *left_root_abspath = left_abspath;
660  const char *right_root_abspath = right_abspath;
661  svn_boolean_t left_before_right = TRUE; /* Future argument? */
662
663  if (depth == svn_depth_unknown)
664    depth = svn_depth_infinity;
665
666  SVN_ERR(svn_io_check_resolved_path(left_abspath, &left_kind, scratch_pool));
667  SVN_ERR(svn_io_check_resolved_path(right_abspath, &right_kind, scratch_pool));
668
669  if (left_kind == svn_node_dir && right_kind == svn_node_dir)
670    {
671      SVN_ERR(do_dir_diff(left_abspath, right_abspath,
672                          left_root_abspath, right_root_abspath,
673                          FALSE, FALSE, left_before_right,
674                          depth, NULL /* parent_baton */,
675                          diff_processor, ctx, scratch_pool));
676    }
677  else if (left_kind == svn_node_file && right_kind == svn_node_file)
678    {
679      SVN_ERR(do_file_diff(left_abspath, right_abspath,
680                           left_root_abspath, right_root_abspath,
681                           FALSE, FALSE,
682                           NULL /* parent_baton */,
683                           diff_processor, ctx, scratch_pool));
684    }
685  else if (left_kind == svn_node_file || left_kind == svn_node_dir
686           || right_kind == svn_node_file || right_kind == svn_node_dir)
687    {
688      /* The root is added/deleted/replaced. Report delete and/or add. */
689      if (left_before_right)
690        {
691          if (left_kind == svn_node_file)
692            SVN_ERR(do_file_diff(left_abspath, right_abspath,
693                                 left_root_abspath, right_root_abspath,
694                                 TRUE, FALSE, NULL /* parent_baton */,
695                                 diff_processor, ctx, scratch_pool));
696          else if (left_kind == svn_node_dir)
697            SVN_ERR(do_dir_diff(left_abspath, right_abspath,
698                                left_root_abspath, right_root_abspath,
699                                TRUE, FALSE, left_before_right,
700                                depth, NULL /* parent_baton */,
701                                diff_processor, ctx, scratch_pool));
702        }
703
704      if (right_kind == svn_node_file)
705        SVN_ERR(do_file_diff(left_abspath, right_abspath,
706                             left_root_abspath, right_root_abspath,
707                             FALSE, TRUE, NULL /* parent_baton */,
708                             diff_processor, ctx, scratch_pool));
709      else if (right_kind == svn_node_dir)
710        SVN_ERR(do_dir_diff(left_abspath, right_abspath,
711                            left_root_abspath, right_root_abspath,
712                            FALSE, TRUE, left_before_right,
713                            depth, NULL /* parent_baton */,
714                            diff_processor, ctx, scratch_pool));
715
716      if (! left_before_right)
717        {
718          if (left_kind == svn_node_file)
719            SVN_ERR(do_file_diff(left_abspath, right_abspath,
720                                 left_root_abspath, right_root_abspath,
721                                 TRUE, FALSE, NULL /* parent_baton */,
722                                 diff_processor, ctx, scratch_pool));
723          else if (left_kind == svn_node_dir)
724            SVN_ERR(do_dir_diff(left_abspath, right_abspath,
725                                left_root_abspath, right_root_abspath,
726                                TRUE, FALSE, left_before_right,
727                                depth, NULL /* parent_baton */,
728                                diff_processor, ctx, scratch_pool));
729        }
730    }
731  else
732    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
733                             _("'%s' is not a file or directory"),
734                             svn_dirent_local_style(
735                                    (left_kind == svn_node_none)
736                                        ? left_abspath
737                                        : right_abspath,
738                                    scratch_pool));
739
740  return SVN_NO_ERROR;
741}
742