]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_client/diff_local.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_client / diff_local.c
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 \f
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_wc_private.h"
49
50 #include "svn_private_config.h"
51
52 \f
53 /* Try to get properties for LOCAL_ABSPATH and return them in the property
54  * hash *PROPS. If there are no properties because LOCAL_ABSPATH is not
55  * versioned, return an empty property hash. */
56 static svn_error_t *
57 get_props(apr_hash_t **props,
58           const char *local_abspath,
59           svn_wc_context_t *wc_ctx,
60           apr_pool_t *result_pool,
61           apr_pool_t *scratch_pool)
62 {
63   svn_error_t *err;
64
65   err = svn_wc_prop_list2(props, wc_ctx, local_abspath, result_pool,
66                           scratch_pool);
67   if (err)
68     {
69       if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
70           err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
71         {
72           svn_error_clear(err);
73           *props = apr_hash_make(result_pool);
74
75           /* ### Apply autoprops, like 'svn add' would? */
76         }
77       else
78         return svn_error_trace(err);
79     }
80
81   return SVN_NO_ERROR;
82 }
83
84 /* Produce a diff between two arbitrary files at LOCAL_ABSPATH1 and
85  * LOCAL_ABSPATH2, using the diff callbacks from CALLBACKS.
86  * Use PATH as the name passed to diff callbacks.
87  * FILE1_IS_EMPTY and FILE2_IS_EMPTY are used as hints which diff callback
88  * function to use to compare the files (added/deleted/changed).
89  *
90  * If ORIGINAL_PROPS_OVERRIDE is not NULL, use it as original properties
91  * instead of reading properties from LOCAL_ABSPATH1. This is required when
92  * a file replaces a directory, where LOCAL_ABSPATH1 is an empty file that
93  * file content must be diffed against, but properties to diff against come
94  * from the replaced directory. */
95 static svn_error_t *
96 do_arbitrary_files_diff(const char *local_abspath1,
97                         const char *local_abspath2,
98                         const char *path,
99                         svn_boolean_t file1_is_empty,
100                         svn_boolean_t file2_is_empty,
101                         apr_hash_t *original_props_override,
102                         const svn_wc_diff_callbacks4_t *callbacks,
103                         void *diff_baton,
104                         svn_client_ctx_t *ctx,
105                         apr_pool_t *scratch_pool)
106 {
107   apr_hash_t *original_props;
108   apr_hash_t *modified_props;
109   apr_array_header_t *prop_changes;
110   svn_string_t *original_mime_type = NULL;
111   svn_string_t *modified_mime_type = NULL;
112
113   if (ctx->cancel_func)
114     SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
115
116   /* Try to get properties from either file. It's OK if the files do not
117    * have properties, or if they are unversioned. */
118   if (original_props_override)
119     original_props = original_props_override;
120   else
121     SVN_ERR(get_props(&original_props, local_abspath1, ctx->wc_ctx,
122                       scratch_pool, scratch_pool));
123   SVN_ERR(get_props(&modified_props, local_abspath2, ctx->wc_ctx,
124                     scratch_pool, scratch_pool));
125
126   SVN_ERR(svn_prop_diffs(&prop_changes, modified_props, original_props,
127                          scratch_pool));
128
129   /* Try to determine the mime-type of each file. */
130   original_mime_type = svn_hash_gets(original_props, SVN_PROP_MIME_TYPE);
131   if (!file1_is_empty && !original_mime_type)
132     {
133       const char *mime_type;
134       SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1,
135                                       ctx->mimetypes_map, scratch_pool));
136
137       if (mime_type)
138         original_mime_type = svn_string_create(mime_type, scratch_pool);
139     }
140
141   modified_mime_type = svn_hash_gets(modified_props, SVN_PROP_MIME_TYPE);
142   if (!file2_is_empty && !modified_mime_type)
143     {
144       const char *mime_type;
145       SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1,
146                                       ctx->mimetypes_map, scratch_pool));
147
148       if (mime_type)
149         modified_mime_type = svn_string_create(mime_type, scratch_pool);
150     }
151
152   /* Produce the diff. */
153   if (file1_is_empty && !file2_is_empty)
154     SVN_ERR(callbacks->file_added(NULL, NULL, NULL, path,
155                                   local_abspath1, local_abspath2,
156                                   /* ### TODO get real revision info
157                                    * for versioned files? */
158                                   SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
159                                   original_mime_type ?
160                                     original_mime_type->data : NULL,
161                                   modified_mime_type ?
162                                     modified_mime_type->data : NULL,
163                                   /* ### TODO get copyfrom? */
164                                   NULL, SVN_INVALID_REVNUM,
165                                   prop_changes, original_props,
166                                   diff_baton, scratch_pool));
167   else if (!file1_is_empty && file2_is_empty)
168     SVN_ERR(callbacks->file_deleted(NULL, NULL, path,
169                                     local_abspath1, local_abspath2,
170                                     original_mime_type ?
171                                       original_mime_type->data : NULL,
172                                     modified_mime_type ?
173                                       modified_mime_type->data : NULL,
174                                     original_props,
175                                     diff_baton, scratch_pool));
176   else
177     {
178       svn_stream_t *file1;
179       svn_stream_t *file2;
180       svn_boolean_t same;
181       svn_string_t *val;
182       /* We have two files, which may or may not be the same.
183
184          ### Our caller assumes that we should ignore symlinks here and
185              handle them as normal paths. Perhaps that should change?
186       */
187       SVN_ERR(svn_stream_open_readonly(&file1, local_abspath1, scratch_pool,
188                                        scratch_pool));
189
190       SVN_ERR(svn_stream_open_readonly(&file2, local_abspath2, scratch_pool,
191                                        scratch_pool));
192
193       /* Wrap with normalization, etc. if necessary */
194       if (original_props)
195         {
196           val = svn_hash_gets(original_props, SVN_PROP_EOL_STYLE);
197
198           if (val)
199             {
200               svn_subst_eol_style_t style;
201               const char *eol;
202               svn_subst_eol_style_from_value(&style, &eol, val->data);
203
204               /* ### Ignoring keywords */
205               if (eol)
206                 file1 = svn_subst_stream_translated(file1, eol, TRUE,
207                                                     NULL, FALSE,
208                                                     scratch_pool);
209             }
210         }
211
212       if (modified_props)
213         {
214           val = svn_hash_gets(modified_props, SVN_PROP_EOL_STYLE);
215
216           if (val)
217             {
218               svn_subst_eol_style_t style;
219               const char *eol;
220               svn_subst_eol_style_from_value(&style, &eol, val->data);
221
222               /* ### Ignoring keywords */
223               if (eol)
224                 file2 = svn_subst_stream_translated(file2, eol, TRUE,
225                                                     NULL, FALSE,
226                                                     scratch_pool);
227             }
228         }
229
230       SVN_ERR(svn_stream_contents_same2(&same, file1, file2, scratch_pool));
231
232       if (! same || prop_changes->nelts > 0)
233         {
234           /* ### We should probably pass the normalized data we created using
235                  the subst streams as that is what diff users expect */
236           SVN_ERR(callbacks->file_changed(NULL, NULL, NULL, path,
237                                           same ? NULL : local_abspath1,
238                                           same ? NULL : local_abspath2,
239                                           /* ### TODO get real revision info
240                                            * for versioned files? */
241                                           SVN_INVALID_REVNUM /* rev1 */,
242                                           SVN_INVALID_REVNUM /* rev2 */,
243                                           original_mime_type ?
244                                             original_mime_type->data : NULL,
245                                           modified_mime_type ?
246                                             modified_mime_type->data : NULL,
247                                           prop_changes, original_props,
248                                           diff_baton, scratch_pool));
249         }
250     }
251
252   return SVN_NO_ERROR;
253 }
254
255 struct arbitrary_diff_walker_baton {
256   /* The root directories of the trees being compared. */
257   const char *root1_abspath;
258   const char *root2_abspath;
259
260   /* TRUE if recursing within an added subtree of root2_abspath that
261    * does not exist in root1_abspath. */
262   svn_boolean_t recursing_within_added_subtree;
263
264   /* TRUE if recursing within an administrative (.i.e. .svn) directory. */
265   svn_boolean_t recursing_within_adm_dir;
266
267   /* The absolute path of the adm dir if RECURSING_WITHIN_ADM_DIR is TRUE.
268    * Else this is NULL.*/
269   const char *adm_dir_abspath;
270
271   /* A path to an empty file used for diffs that add/delete files. */
272   const char *empty_file_abspath;
273
274   const svn_wc_diff_callbacks4_t *callbacks;
275   void *diff_baton;
276   svn_client_ctx_t *ctx;
277   apr_pool_t *pool;
278 } arbitrary_diff_walker_baton;
279
280 /* Forward declaration needed because this function has a cyclic
281  * dependency with do_arbitrary_dirs_diff(). */
282 static svn_error_t *
283 arbitrary_diff_walker(void *baton, const char *local_abspath,
284                       const apr_finfo_t *finfo,
285                       apr_pool_t *scratch_pool);
286
287 /* Another forward declaration. */
288 static svn_error_t *
289 arbitrary_diff_this_dir(struct arbitrary_diff_walker_baton *b,
290                         const char *local_abspath,
291                         svn_depth_t depth,
292                         apr_pool_t *scratch_pool);
293
294 /* Produce a diff of depth DEPTH between two arbitrary directories at
295  * LOCAL_ABSPATH1 and LOCAL_ABSPATH2, using the provided diff callbacks
296  * to show file changes and, for versioned nodes, property changes.
297  *
298  * If ROOT_ABSPATH1 and ROOT_ABSPATH2 are not NULL, show paths in diffs
299  * relative to these roots, rather than relative to LOCAL_ABSPATH1 and
300  * LOCAL_ABSPATH2. This is needed when crawling a subtree that exists
301  * only within LOCAL_ABSPATH2. */
302 static svn_error_t *
303 do_arbitrary_dirs_diff(const char *local_abspath1,
304                        const char *local_abspath2,
305                        const char *root_abspath1,
306                        const char *root_abspath2,
307                        svn_depth_t depth,
308                        const svn_wc_diff_callbacks4_t *callbacks,
309                        void *diff_baton,
310                        svn_client_ctx_t *ctx,
311                        apr_pool_t *scratch_pool)
312 {
313   apr_file_t *empty_file;
314   svn_node_kind_t kind1;
315
316   struct arbitrary_diff_walker_baton b;
317
318   /* If LOCAL_ABSPATH1 is not a directory, crawl LOCAL_ABSPATH2 instead
319    * and compare it to LOCAL_ABSPATH1, showing only additions.
320    * This case can only happen during recursion from arbitrary_diff_walker(),
321    * because do_arbitrary_nodes_diff() prevents this from happening at
322    * the root of the comparison. */
323   SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
324   b.recursing_within_added_subtree = (kind1 != svn_node_dir);
325
326   b.root1_abspath = root_abspath1 ? root_abspath1 : local_abspath1;
327   b.root2_abspath = root_abspath2 ? root_abspath2 : local_abspath2;
328   b.recursing_within_adm_dir = FALSE;
329   b.adm_dir_abspath = NULL;
330   b.callbacks = callbacks;
331   b.diff_baton = diff_baton;
332   b.ctx = ctx;
333   b.pool = scratch_pool;
334
335   SVN_ERR(svn_io_open_unique_file3(&empty_file, &b.empty_file_abspath,
336                                    NULL, svn_io_file_del_on_pool_cleanup,
337                                    scratch_pool, scratch_pool));
338
339   if (depth <= svn_depth_immediates)
340     SVN_ERR(arbitrary_diff_this_dir(&b, local_abspath1, depth, scratch_pool));
341   else if (depth == svn_depth_infinity)
342     SVN_ERR(svn_io_dir_walk2(b.recursing_within_added_subtree ? local_abspath2
343                                                               : local_abspath1,
344                              0, arbitrary_diff_walker, &b, scratch_pool));
345   return SVN_NO_ERROR;
346 }
347
348 /* Produce a diff of depth DEPTH for the directory at LOCAL_ABSPATH,
349  * using information from the arbitrary_diff_walker_baton B.
350  * LOCAL_ABSPATH is the path being crawled and can be on either side
351  * of the diff depending on baton->recursing_within_added_subtree. */
352 static svn_error_t *
353 arbitrary_diff_this_dir(struct arbitrary_diff_walker_baton *b,
354                         const char *local_abspath,
355                         svn_depth_t depth,
356                         apr_pool_t *scratch_pool)
357 {
358   const char *local_abspath1;
359   const char *local_abspath2;
360   svn_node_kind_t kind1;
361   svn_node_kind_t kind2;
362   const char *child_relpath;
363   apr_hash_t *dirents1;
364   apr_hash_t *dirents2;
365   apr_hash_t *merged_dirents;
366   apr_array_header_t *sorted_dirents;
367   int i;
368   apr_pool_t *iterpool;
369
370   if (b->recursing_within_adm_dir)
371     {
372       if (svn_dirent_skip_ancestor(b->adm_dir_abspath, local_abspath))
373         return SVN_NO_ERROR;
374       else
375         {
376           b->recursing_within_adm_dir = FALSE;
377           b->adm_dir_abspath = NULL;
378         }
379     }
380   else if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL),
381                              scratch_pool))
382     {
383       b->recursing_within_adm_dir = TRUE;
384       b->adm_dir_abspath = apr_pstrdup(b->pool, local_abspath);
385       return SVN_NO_ERROR;
386     }
387
388   if (b->recursing_within_added_subtree)
389     child_relpath = svn_dirent_skip_ancestor(b->root2_abspath, local_abspath);
390   else
391     child_relpath = svn_dirent_skip_ancestor(b->root1_abspath, local_abspath);
392   if (!child_relpath)
393     return SVN_NO_ERROR;
394
395   local_abspath1 = svn_dirent_join(b->root1_abspath, child_relpath,
396                                    scratch_pool);
397   SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
398
399   local_abspath2 = svn_dirent_join(b->root2_abspath, child_relpath,
400                                    scratch_pool);
401   SVN_ERR(svn_io_check_resolved_path(local_abspath2, &kind2, scratch_pool));
402
403   if (depth > svn_depth_empty)
404     {
405       if (kind1 == svn_node_dir)
406         SVN_ERR(svn_io_get_dirents3(&dirents1, local_abspath1,
407                                     TRUE, /* only_check_type */
408                                     scratch_pool, scratch_pool));
409       else
410         dirents1 = apr_hash_make(scratch_pool);
411     }
412
413   if (kind2 == svn_node_dir)
414     {
415       apr_hash_t *original_props;
416       apr_hash_t *modified_props;
417       apr_array_header_t *prop_changes;
418
419       /* Show any property changes for this directory. */
420       SVN_ERR(get_props(&original_props, local_abspath1, b->ctx->wc_ctx,
421                         scratch_pool, scratch_pool));
422       SVN_ERR(get_props(&modified_props, local_abspath2, b->ctx->wc_ctx,
423                         scratch_pool, scratch_pool));
424       SVN_ERR(svn_prop_diffs(&prop_changes, modified_props, original_props,
425                              scratch_pool));
426       if (prop_changes->nelts > 0)
427         SVN_ERR(b->callbacks->dir_props_changed(NULL, NULL, child_relpath,
428                                                 FALSE /* was_added */,
429                                                 prop_changes, original_props,
430                                                 b->diff_baton,
431                                                 scratch_pool));
432
433       if (depth > svn_depth_empty)
434         {
435           /* Read directory entries. */
436           SVN_ERR(svn_io_get_dirents3(&dirents2, local_abspath2,
437                                       TRUE, /* only_check_type */
438                                       scratch_pool, scratch_pool));
439         }
440     }
441   else if (depth > svn_depth_empty)
442     dirents2 = apr_hash_make(scratch_pool);
443
444   if (depth <= svn_depth_empty)
445     return SVN_NO_ERROR;
446
447   /* Compare dirents1 to dirents2 and show added/deleted/changed files. */
448   merged_dirents = apr_hash_merge(scratch_pool, dirents1, dirents2,
449                                   NULL, NULL);
450   sorted_dirents = svn_sort__hash(merged_dirents,
451                                   svn_sort_compare_items_as_paths,
452                                   scratch_pool);
453   iterpool = svn_pool_create(scratch_pool);
454   for (i = 0; i < sorted_dirents->nelts; i++)
455     {
456       svn_sort__item_t elt = APR_ARRAY_IDX(sorted_dirents, i, svn_sort__item_t);
457       const char *name = elt.key;
458       svn_io_dirent2_t *dirent1;
459       svn_io_dirent2_t *dirent2;
460       const char *child1_abspath;
461       const char *child2_abspath;
462
463       svn_pool_clear(iterpool);
464
465       if (b->ctx->cancel_func)
466         SVN_ERR(b->ctx->cancel_func(b->ctx->cancel_baton));
467
468       if (strcmp(name, SVN_WC_ADM_DIR_NAME) == 0)
469         continue;
470
471       dirent1 = svn_hash_gets(dirents1, name);
472       if (!dirent1)
473         {
474           dirent1 = svn_io_dirent2_create(iterpool);
475           dirent1->kind = svn_node_none;
476         }
477       dirent2 = svn_hash_gets(dirents2, name);
478       if (!dirent2)
479         {
480           dirent2 = svn_io_dirent2_create(iterpool);
481           dirent2->kind = svn_node_none;
482         }
483
484       child1_abspath = svn_dirent_join(local_abspath1, name, iterpool);
485       child2_abspath = svn_dirent_join(local_abspath2, name, iterpool);
486
487       if (dirent1->special)
488         SVN_ERR(svn_io_check_resolved_path(child1_abspath, &dirent1->kind,
489                                            iterpool));
490       if (dirent2->special)
491         SVN_ERR(svn_io_check_resolved_path(child1_abspath, &dirent2->kind,
492                                            iterpool));
493
494       if (dirent1->kind == svn_node_dir &&
495           dirent2->kind == svn_node_dir)
496         {
497           if (depth == svn_depth_immediates)
498             {
499               /* Not using the walker, so show property diffs on these dirs. */
500               SVN_ERR(do_arbitrary_dirs_diff(child1_abspath, child2_abspath,
501                                              b->root1_abspath, b->root2_abspath,
502                                              svn_depth_empty,
503                                              b->callbacks, b->diff_baton,
504                                              b->ctx, iterpool));
505             }
506           else
507             {
508               /* Either the walker will visit these directories (with
509                * depth=infinity) and they will be processed as 'this dir'
510                * later, or we're showing file children only (depth=files). */
511               continue;
512             }
513
514         }
515
516       /* Files that exist only in dirents1. */
517       if (dirent1->kind == svn_node_file &&
518           (dirent2->kind == svn_node_dir || dirent2->kind == svn_node_none))
519         SVN_ERR(do_arbitrary_files_diff(child1_abspath, b->empty_file_abspath,
520                                         svn_relpath_join(child_relpath, name,
521                                                          iterpool),
522                                         FALSE, TRUE, NULL,
523                                         b->callbacks, b->diff_baton,
524                                         b->ctx, iterpool));
525
526       /* Files that exist only in dirents2. */
527       if (dirent2->kind == svn_node_file &&
528           (dirent1->kind == svn_node_dir || dirent1->kind == svn_node_none))
529         {
530           apr_hash_t *original_props;
531
532           SVN_ERR(get_props(&original_props, child1_abspath, b->ctx->wc_ctx,
533                             scratch_pool, scratch_pool));
534           SVN_ERR(do_arbitrary_files_diff(b->empty_file_abspath, child2_abspath,
535                                           svn_relpath_join(child_relpath, name,
536                                                            iterpool),
537                                           TRUE, FALSE, original_props,
538                                           b->callbacks, b->diff_baton,
539                                           b->ctx, iterpool));
540         }
541
542       /* Files that exist in dirents1 and dirents2. */
543       if (dirent1->kind == svn_node_file && dirent2->kind == svn_node_file)
544         SVN_ERR(do_arbitrary_files_diff(child1_abspath, child2_abspath,
545                                         svn_relpath_join(child_relpath, name,
546                                                          iterpool),
547                                         FALSE, FALSE, NULL,
548                                         b->callbacks, b->diff_baton,
549                                         b->ctx, scratch_pool));
550
551       /* Directories that only exist in dirents2. These aren't crawled
552        * by this walker so we have to crawl them separately. */
553       if (depth > svn_depth_files &&
554           dirent2->kind == svn_node_dir &&
555           (dirent1->kind == svn_node_file || dirent1->kind == svn_node_none))
556         SVN_ERR(do_arbitrary_dirs_diff(child1_abspath, child2_abspath,
557                                        b->root1_abspath, b->root2_abspath,
558                                        depth <= svn_depth_immediates
559                                          ? svn_depth_empty
560                                          : svn_depth_infinity ,
561                                        b->callbacks, b->diff_baton,
562                                        b->ctx, iterpool));
563     }
564
565   svn_pool_destroy(iterpool);
566
567   return SVN_NO_ERROR;
568 }
569
570 /* An implementation of svn_io_walk_func_t.
571  * Note: LOCAL_ABSPATH is the path being crawled and can be on either side
572  * of the diff depending on baton->recursing_within_added_subtree. */
573 static svn_error_t *
574 arbitrary_diff_walker(void *baton, const char *local_abspath,
575                       const apr_finfo_t *finfo,
576                       apr_pool_t *scratch_pool)
577 {
578   struct arbitrary_diff_walker_baton *b = baton;
579
580   if (b->ctx->cancel_func)
581     SVN_ERR(b->ctx->cancel_func(b->ctx->cancel_baton));
582
583   if (finfo->filetype != APR_DIR)
584     return SVN_NO_ERROR;
585
586   SVN_ERR(arbitrary_diff_this_dir(b, local_abspath, svn_depth_infinity,
587                                   scratch_pool));
588
589   return SVN_NO_ERROR;
590 }
591
592 svn_error_t *
593 svn_client__arbitrary_nodes_diff(const char *local_abspath1,
594                                  const char *local_abspath2,
595                                  svn_depth_t depth,
596                                  const svn_wc_diff_callbacks4_t *callbacks,
597                                  void *diff_baton,
598                                  svn_client_ctx_t *ctx,
599                                  apr_pool_t *scratch_pool)
600 {
601   svn_node_kind_t kind1;
602   svn_node_kind_t kind2;
603
604   SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
605   SVN_ERR(svn_io_check_resolved_path(local_abspath2, &kind2, scratch_pool));
606
607   if (kind1 != kind2)
608     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
609                              _("'%s' is not the same node kind as '%s'"),
610                              svn_dirent_local_style(local_abspath1,
611                                                     scratch_pool),
612                              svn_dirent_local_style(local_abspath2,
613                                                     scratch_pool));
614
615   if (depth == svn_depth_unknown)
616     depth = svn_depth_infinity;
617
618   if (kind1 == svn_node_file)
619     SVN_ERR(do_arbitrary_files_diff(local_abspath1, local_abspath2,
620                                     svn_dirent_basename(local_abspath1,
621                                                         scratch_pool),
622                                     FALSE, FALSE, NULL,
623                                     callbacks, diff_baton,
624                                     ctx, scratch_pool));
625   else if (kind1 == svn_node_dir)
626     SVN_ERR(do_arbitrary_dirs_diff(local_abspath1, local_abspath2,
627                                    NULL, NULL, depth,
628                                    callbacks, diff_baton,
629                                    ctx, scratch_pool));
630   else
631     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
632                              _("'%s' is not a file or directory"),
633                              kind1 == svn_node_none
634                                ? svn_dirent_local_style(local_abspath1,
635                                                         scratch_pool)
636                                : svn_dirent_local_style(local_abspath2,
637                                                         scratch_pool));
638   return SVN_NO_ERROR;
639 }