]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_wc/diff_editor.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_wc / diff_editor.c
1 /*
2  * diff_editor.c -- The diff editor for comparing the working copy against the
3  *                  repository.
4  *
5  * ====================================================================
6  *    Licensed to the Apache Software Foundation (ASF) under one
7  *    or more contributor license agreements.  See the NOTICE file
8  *    distributed with this work for additional information
9  *    regarding copyright ownership.  The ASF licenses this file
10  *    to you under the Apache License, Version 2.0 (the
11  *    "License"); you may not use this file except in compliance
12  *    with the License.  You may obtain a copy of the License at
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
16  *    Unless required by applicable law or agreed to in writing,
17  *    software distributed under the License is distributed on an
18  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19  *    KIND, either express or implied.  See the License for the
20  *    specific language governing permissions and limitations
21  *    under the License.
22  * ====================================================================
23  */
24
25 /*
26  * This code uses an svn_delta_editor_t editor driven by
27  * svn_wc_crawl_revisions (like the update command) to retrieve the
28  * differences between the working copy and the requested repository
29  * version. Rather than updating the working copy, this new editor creates
30  * temporary files that contain the pristine repository versions. When the
31  * crawler closes the files the editor calls back to a client layer
32  * function to compare the working copy and the temporary file. There is
33  * only ever one temporary file in existence at any time.
34  *
35  * When the crawler closes a directory, the editor then calls back to the
36  * client layer to compare any remaining files that may have been modified
37  * locally. Added directories do not have corresponding temporary
38  * directories created, as they are not needed.
39  *
40  * The diff result from this editor is a combination of the restructuring
41  * operations from the repository with the local restructurings since checking
42  * out.
43  *
44  * ### TODO: Make sure that we properly support and report multi layered
45  *           operations instead of only simple file replacements.
46  *
47  * ### TODO: Replacements where the node kind changes needs support. It
48  * mostly works when the change is in the repository, but not when it is
49  * in the working copy.
50  *
51  * ### TODO: Do we need to support copyfrom?
52  *
53  */
54
55 #include <apr_hash.h>
56 #include <apr_md5.h>
57
58 #include <assert.h>
59
60 #include "svn_error.h"
61 #include "svn_pools.h"
62 #include "svn_dirent_uri.h"
63 #include "svn_path.h"
64 #include "svn_hash.h"
65 #include "svn_sorts.h"
66
67 #include "private/svn_diff_tree.h"
68 #include "private/svn_editor.h"
69 #include "private/svn_sorts_private.h"
70 #include "private/svn_subr_private.h"
71 #include "private/svn_wc_private.h"
72
73 #include "wc.h"
74 #include "props.h"
75 #include "adm_files.h"
76 #include "translate.h"
77 #include "diff.h"
78
79 #include "svn_private_config.h"
80
81 /*-------------------------------------------------------------------------*/
82
83 \f
84 /* Overall crawler editor baton.
85  */
86 struct edit_baton_t
87 {
88   /* A wc db. */
89   svn_wc__db_t *db;
90
91   /* A diff tree processor, receiving the result of the diff. */
92   const svn_diff_tree_processor_t *processor;
93
94   /* A boolean indicating whether local additions should be reported before
95      remote deletes. The processor can transform adds in deletes and deletes
96      in adds, but it can't reorder the output. */
97   svn_boolean_t local_before_remote;
98
99   /* ANCHOR/TARGET represent the base of the hierarchy to be compared. */
100   const char *target;
101   const char *anchor_abspath;
102
103   /* Target revision */
104   svn_revnum_t revnum;
105
106   /* Was the root opened? */
107   svn_boolean_t root_opened;
108
109   /* How does this diff descend as seen from target? */
110   svn_depth_t depth;
111
112   /* Should this diff ignore node ancestry? */
113   svn_boolean_t ignore_ancestry;
114
115   /* Possibly diff repos against text-bases instead of working files. */
116   svn_boolean_t diff_pristine;
117
118   /* Cancel function/baton */
119   svn_cancel_func_t cancel_func;
120   void *cancel_baton;
121
122   apr_pool_t *pool;
123 };
124
125 /* Directory level baton.
126  */
127 struct dir_baton_t
128 {
129   /* Reference to parent directory baton (or NULL for the root) */
130   struct dir_baton_t *parent_baton;
131
132   /* The depth at which this directory should be diffed. */
133   svn_depth_t depth;
134
135   /* The name and path of this directory as if they would be/are in the
136       local working copy. */
137   const char *name;
138   const char *relpath;
139   const char *local_abspath;
140
141   /* TRUE if the file is added by the editor drive. */
142   svn_boolean_t added;
143   /* TRUE if the node exists only on the repository side (op_depth 0 or added) */
144   svn_boolean_t repos_only;
145   /* TRUE if the node is to be compared with an unrelated node*/
146   svn_boolean_t ignoring_ancestry;
147
148   /* Processor state */
149   void *pdb;
150   svn_boolean_t skip;
151   svn_boolean_t skip_children;
152
153   svn_diff_source_t *left_src;
154   svn_diff_source_t *right_src;
155
156   apr_hash_t *local_info;
157
158   /* A hash containing the basenames of the nodes reported deleted by the
159      repository (or NULL for no values). */
160   apr_hash_t *deletes;
161
162   /* Identifies those directory elements that get compared while running
163      the crawler.  These elements should not be compared again when
164      recursively looking for local modifications.
165
166      This hash maps the basename of the node to an unimportant value.
167
168      If the directory's properties have been compared, an item with hash
169      key of "" will be present in the hash. */
170   apr_hash_t *compared;
171
172   /* The list of incoming BASE->repos propchanges. */
173   apr_array_header_t *propchanges;
174
175   /* Has a change on regular properties */
176   svn_boolean_t has_propchange;
177
178   /* The overall crawler editor baton. */
179   struct edit_baton_t *eb;
180
181   apr_pool_t *pool;
182   int users;
183 };
184
185 /* File level baton.
186  */
187 struct file_baton_t
188 {
189   struct dir_baton_t *parent_baton;
190
191   /* The name and path of this file as if they would be/are in the
192      parent directory, diff session and local working copy. */
193   const char *name;
194   const char *relpath;
195   const char *local_abspath;
196
197   /* Processor state */
198   void *pfb;
199   svn_boolean_t skip;
200
201   /* TRUE if the file is added by the editor drive. */
202   svn_boolean_t added;
203   /* TRUE if the node exists only on the repository side (op_depth 0 or added) */
204   svn_boolean_t repos_only;
205   /* TRUE if the node is to be compared with an unrelated node*/
206   svn_boolean_t ignoring_ancestry;
207
208   const svn_diff_source_t *left_src;
209   const svn_diff_source_t *right_src;
210
211   /* The list of incoming BASE->repos propchanges. */
212   apr_array_header_t *propchanges;
213
214   /* Has a change on regular properties */
215   svn_boolean_t has_propchange;
216
217   /* The current BASE checksum and props */
218   const svn_checksum_t *base_checksum;
219   apr_hash_t *base_props;
220
221   /* The resulting from apply_textdelta */
222   const char *temp_file_path;
223   unsigned char result_digest[APR_MD5_DIGESTSIZE];
224
225   /* The overall crawler editor baton. */
226   struct edit_baton_t *eb;
227
228   apr_pool_t *pool;
229 };
230
231 /* Create a new edit baton. TARGET_PATH/ANCHOR are working copy paths
232  * that describe the root of the comparison. CALLBACKS/CALLBACK_BATON
233  * define the callbacks to compare files. DEPTH defines if and how to
234  * descend into subdirectories; see public doc string for exactly how.
235  * IGNORE_ANCESTRY defines whether to utilize node ancestry when
236  * calculating diffs.  USE_TEXT_BASE defines whether to compare
237  * against working files or text-bases.  REVERSE_ORDER defines which
238  * direction to perform the diff.
239  */
240 static svn_error_t *
241 make_edit_baton(struct edit_baton_t **edit_baton,
242                 svn_wc__db_t *db,
243                 const char *anchor_abspath,
244                 const char *target,
245                 const svn_diff_tree_processor_t *diff_processor,
246                 svn_depth_t depth,
247                 svn_boolean_t ignore_ancestry,
248                 svn_boolean_t use_text_base,
249                 svn_boolean_t reverse_order,
250                 svn_cancel_func_t cancel_func,
251                 void *cancel_baton,
252                 apr_pool_t *pool)
253 {
254   struct edit_baton_t *eb;
255
256   SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath));
257
258   eb = apr_pcalloc(pool, sizeof(*eb));
259   eb->db = db;
260   eb->anchor_abspath = apr_pstrdup(pool, anchor_abspath);
261   eb->target = apr_pstrdup(pool, target);
262   eb->processor = diff_processor;
263   eb->depth = depth;
264   eb->ignore_ancestry = ignore_ancestry;
265   eb->local_before_remote = reverse_order;
266   eb->diff_pristine = use_text_base;
267   eb->cancel_func = cancel_func;
268   eb->cancel_baton = cancel_baton;
269   eb->pool = pool;
270
271   *edit_baton = eb;
272   return SVN_NO_ERROR;
273 }
274
275 /* Create a new directory baton.  PATH is the directory path,
276  * including anchor_path.  ADDED is set if this directory is being
277  * added rather than replaced.  PARENT_BATON is the baton of the
278  * parent directory, it will be null if this is the root of the
279  * comparison hierarchy.  The directory and its parent may or may not
280  * exist in the working copy.  EDIT_BATON is the overall crawler
281  * editor baton.
282  */
283 static struct dir_baton_t *
284 make_dir_baton(const char *path,
285                struct dir_baton_t *parent_baton,
286                struct edit_baton_t *eb,
287                svn_boolean_t added,
288                svn_depth_t depth,
289                apr_pool_t *result_pool)
290 {
291   apr_pool_t *dir_pool = svn_pool_create(parent_baton ? parent_baton->pool
292                                                       : eb->pool);
293   struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
294
295   db->parent_baton = parent_baton;
296
297   /* Allocate 1 string for using as 3 strings */
298   db->local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool);
299   db->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, db->local_abspath);
300   db->name = svn_dirent_basename(db->relpath, NULL);
301
302   db->eb = eb;
303   db->added = added;
304   db->depth = depth;
305   db->pool = dir_pool;
306   db->propchanges = apr_array_make(dir_pool, 8, sizeof(svn_prop_t));
307   db->compared = apr_hash_make(dir_pool);
308
309   if (parent_baton != NULL)
310     {
311       parent_baton->users++;
312     }
313
314   db->users = 1;
315
316   return db;
317 }
318
319 /* Create a new file baton.  PATH is the file path, including
320  * anchor_path.  ADDED is set if this file is being added rather than
321  * replaced.  PARENT_BATON is the baton of the parent directory.
322  * The directory and its parent may or may not exist in the working copy.
323  */
324 static struct file_baton_t *
325 make_file_baton(const char *path,
326                 svn_boolean_t added,
327                 struct dir_baton_t *parent_baton,
328                 apr_pool_t *result_pool)
329 {
330   apr_pool_t *file_pool = svn_pool_create(result_pool);
331   struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb));
332   struct edit_baton_t *eb = parent_baton->eb;
333
334   fb->eb = eb;
335   fb->parent_baton = parent_baton;
336   fb->parent_baton->users++;
337
338   /* Allocate 1 string for using as 3 strings */
339   fb->local_abspath = svn_dirent_join(eb->anchor_abspath, path, file_pool);
340   fb->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, fb->local_abspath);
341   fb->name = svn_dirent_basename(fb->relpath, NULL);
342
343   fb->added = added;
344   fb->pool = file_pool;
345   fb->propchanges  = apr_array_make(file_pool, 8, sizeof(svn_prop_t));
346
347   return fb;
348 }
349
350 /* Destroy DB when there are no more registered users */
351 static svn_error_t *
352 maybe_done(struct dir_baton_t *db)
353 {
354   db->users--;
355
356   if (!db->users)
357     {
358       struct dir_baton_t *pb = db->parent_baton;
359
360       svn_pool_clear(db->pool);
361
362       if (pb != NULL)
363         SVN_ERR(maybe_done(pb));
364     }
365
366   return SVN_NO_ERROR;
367 }
368
369 /* Standard check to see if a node is represented in the local working copy */
370 #define NOT_PRESENT(status)                                    \
371             ((status) == svn_wc__db_status_not_present          \
372              || (status) == svn_wc__db_status_excluded          \
373              || (status) == svn_wc__db_status_server_excluded)
374
375 svn_error_t *
376 svn_wc__diff_base_working_diff(svn_wc__db_t *db,
377                                const char *local_abspath,
378                                const char *relpath,
379                                svn_revnum_t revision,
380                                const svn_diff_tree_processor_t *processor,
381                                void *processor_dir_baton,
382                                svn_boolean_t diff_pristine,
383                                svn_cancel_func_t cancel_func,
384                                void *cancel_baton,
385                                apr_pool_t *scratch_pool)
386 {
387   void *file_baton = NULL;
388   svn_boolean_t skip = FALSE;
389   svn_wc__db_status_t status;
390   svn_revnum_t db_revision;
391   svn_boolean_t had_props;
392   svn_boolean_t props_mod;
393   svn_boolean_t files_same = FALSE;
394   svn_wc__db_status_t base_status;
395   const svn_checksum_t *working_checksum;
396   const svn_checksum_t *checksum;
397   svn_filesize_t recorded_size;
398   apr_time_t recorded_time;
399   const char *pristine_file;
400   const char *local_file;
401   svn_diff_source_t *left_src;
402   svn_diff_source_t *right_src;
403   apr_hash_t *base_props;
404   apr_hash_t *local_props;
405   apr_array_header_t *prop_changes;
406
407   SVN_ERR(svn_wc__db_read_info(&status, NULL, &db_revision, NULL, NULL, NULL,
408                                NULL, NULL, NULL, NULL, &working_checksum, NULL,
409                                NULL, NULL, NULL, NULL, NULL, &recorded_size,
410                                &recorded_time, NULL, NULL, NULL,
411                                &had_props, &props_mod, NULL, NULL, NULL,
412                                db, local_abspath, scratch_pool, scratch_pool));
413   checksum = working_checksum;
414
415   assert(status == svn_wc__db_status_normal
416          || status == svn_wc__db_status_added
417          || (status == svn_wc__db_status_deleted && diff_pristine));
418
419   if (status != svn_wc__db_status_normal)
420     {
421       SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &db_revision,
422                                        NULL, NULL, NULL, NULL, NULL, NULL,
423                                        NULL, &checksum, NULL, NULL, &had_props,
424                                        NULL, NULL,
425                                        db, local_abspath,
426                                        scratch_pool, scratch_pool));
427       recorded_size = SVN_INVALID_FILESIZE;
428       recorded_time = 0;
429       props_mod = TRUE; /* Requires compare */
430     }
431   else if (diff_pristine)
432     files_same = TRUE;
433   else
434     {
435       const svn_io_dirent2_t *dirent;
436
437       /* Verify truename to mimic status for iota/IOTA difference on Windows */
438       SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath,
439                                   TRUE /* verify truename */,
440                                   TRUE /* ingore_enoent */,
441                                   scratch_pool, scratch_pool));
442
443       /* If a file does not exist on disk (missing/obstructed) then we
444          can't provide a text diff */
445       if (dirent->kind != svn_node_file
446           || (dirent->kind == svn_node_file
447               && dirent->filesize == recorded_size
448               && dirent->mtime == recorded_time))
449         {
450           files_same = TRUE;
451         }
452     }
453
454   if (files_same && !props_mod)
455     return SVN_NO_ERROR; /* Cheap exit */
456
457   assert(checksum);
458
459   if (!SVN_IS_VALID_REVNUM(revision))
460     revision = db_revision;
461
462   left_src = svn_diff__source_create(revision, scratch_pool);
463   right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
464
465   SVN_ERR(processor->file_opened(&file_baton, &skip, relpath,
466                                  left_src,
467                                  right_src,
468                                  NULL /* copyfrom_src */,
469                                  processor_dir_baton,
470                                  processor,
471                                  scratch_pool, scratch_pool));
472
473   if (skip)
474     return SVN_NO_ERROR;
475
476   SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file,
477                                        db, local_abspath, checksum,
478                                        scratch_pool, scratch_pool));
479
480   if (diff_pristine)
481     SVN_ERR(svn_wc__db_pristine_get_path(&local_file,
482                                          db, local_abspath,
483                                          working_checksum,
484                                          scratch_pool, scratch_pool));
485   else if (! (had_props || props_mod))
486     local_file = local_abspath;
487   else if (files_same)
488     local_file = pristine_file;
489   else
490     SVN_ERR(svn_wc__internal_translated_file(
491                             &local_file, local_abspath,
492                             db, local_abspath,
493                             SVN_WC_TRANSLATE_TO_NF
494                                 | SVN_WC_TRANSLATE_USE_GLOBAL_TMP,
495                             cancel_func, cancel_baton,
496                             scratch_pool, scratch_pool));
497
498   if (! files_same)
499     SVN_ERR(svn_io_files_contents_same_p(&files_same, local_file,
500                                          pristine_file, scratch_pool));
501
502   if (had_props)
503     SVN_ERR(svn_wc__db_base_get_props(&base_props, db, local_abspath,
504                                       scratch_pool, scratch_pool));
505   else
506     base_props = apr_hash_make(scratch_pool);
507
508   if (status == svn_wc__db_status_normal && (diff_pristine || !props_mod))
509     local_props = base_props;
510   else if (diff_pristine)
511     SVN_ERR(svn_wc__db_read_pristine_props(&local_props, db, local_abspath,
512                                            scratch_pool, scratch_pool));
513   else
514     SVN_ERR(svn_wc__db_read_props(&local_props, db, local_abspath,
515                                   scratch_pool, scratch_pool));
516
517   SVN_ERR(svn_prop_diffs(&prop_changes, local_props, base_props, scratch_pool));
518
519   if (prop_changes->nelts || !files_same)
520     {
521       SVN_ERR(processor->file_changed(relpath,
522                                       left_src,
523                                       right_src,
524                                       pristine_file,
525                                       local_file,
526                                       base_props,
527                                       local_props,
528                                       ! files_same,
529                                       prop_changes,
530                                       file_baton,
531                                       processor,
532                                       scratch_pool));
533     }
534   else
535     {
536       SVN_ERR(processor->file_closed(relpath,
537                                      left_src,
538                                      right_src,
539                                      file_baton,
540                                      processor,
541                                      scratch_pool));
542     }
543
544   return SVN_NO_ERROR;
545 }
546
547 static svn_error_t *
548 ensure_local_info(struct dir_baton_t *db,
549                   apr_pool_t *scratch_pool)
550 {
551   apr_hash_t *conflicts;
552
553   if (db->local_info)
554     return SVN_NO_ERROR;
555
556   SVN_ERR(svn_wc__db_read_children_info(&db->local_info, &conflicts,
557                                         db->eb->db, db->local_abspath,
558                                         FALSE /* base_tree_only */,
559                                         db->pool, scratch_pool));
560
561   return SVN_NO_ERROR;
562 }
563
564 /* Called when the directory is closed to compare any elements that have
565  * not yet been compared.  This identifies local, working copy only
566  * changes.  At this stage we are dealing with files/directories that do
567  * exist in the working copy.
568  *
569  * DIR_BATON is the baton for the directory.
570  */
571 static svn_error_t *
572 walk_local_nodes_diff(struct edit_baton_t *eb,
573                       const char *local_abspath,
574                       const char *path,
575                       svn_depth_t depth,
576                       apr_hash_t *compared,
577                       void *parent_baton,
578                       apr_pool_t *scratch_pool)
579 {
580   svn_wc__db_t *db = eb->db;
581   svn_boolean_t in_anchor_not_target;
582   apr_pool_t *iterpool;
583   void *dir_baton = NULL;
584   svn_boolean_t skip = FALSE;
585   svn_boolean_t skip_children = FALSE;
586   svn_revnum_t revision;
587   svn_boolean_t props_mod;
588   svn_diff_source_t *left_src;
589   svn_diff_source_t *right_src;
590
591   /* Everything we do below is useless if we are comparing to BASE. */
592   if (eb->diff_pristine)
593     return SVN_NO_ERROR;
594
595   /* Determine if this is the anchor directory if the anchor is different
596      to the target. When the target is a file, the anchor is the parent
597      directory and if this is that directory the non-target entries must be
598      skipped. */
599   in_anchor_not_target = ((*path == '\0') && (*eb->target != '\0'));
600
601   iterpool = svn_pool_create(scratch_pool);
602
603   SVN_ERR(svn_wc__db_read_info(NULL, NULL, &revision, NULL, NULL, NULL, NULL,
604                                NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
605                                NULL, NULL, NULL, NULL, NULL, NULL, NULL,
606                                NULL, &props_mod, NULL, NULL, NULL,
607                                db, local_abspath, scratch_pool, scratch_pool));
608
609   left_src = svn_diff__source_create(revision, scratch_pool);
610   right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
611
612   if (compared)
613     {
614       dir_baton = parent_baton;
615       skip = TRUE;
616     }
617   else if (!in_anchor_not_target)
618     SVN_ERR(eb->processor->dir_opened(&dir_baton, &skip, &skip_children,
619                                       path,
620                                       left_src,
621                                       right_src,
622                                       NULL /* copyfrom_src */,
623                                       parent_baton,
624                                       eb->processor,
625                                       scratch_pool, scratch_pool));
626
627
628   if (!skip_children && depth != svn_depth_empty)
629     {
630       apr_hash_t *nodes;
631       apr_hash_t *conflicts;
632       apr_array_header_t *children;
633       svn_depth_t depth_below_here = depth;
634       svn_boolean_t diff_files;
635       svn_boolean_t diff_dirs;
636       int i;
637
638       if (depth_below_here == svn_depth_immediates)
639         depth_below_here = svn_depth_empty;
640
641       diff_files = (depth == svn_depth_unknown
642                    || depth >= svn_depth_files);
643       diff_dirs = (depth == svn_depth_unknown
644                    || depth >= svn_depth_immediates);
645
646       SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
647                                             db, local_abspath,
648                                             FALSE /* base_tree_only */,
649                                             scratch_pool, iterpool));
650
651       children = svn_sort__hash(nodes, svn_sort_compare_items_lexically,
652                             scratch_pool);
653
654       for (i = 0; i < children->nelts; i++)
655         {
656           svn_sort__item_t *item = &APR_ARRAY_IDX(children, i,
657                                                   svn_sort__item_t);
658           const char *name = item->key;
659           struct svn_wc__db_info_t *info = item->value;
660
661           const char *child_abspath;
662           const char *child_relpath;
663           svn_boolean_t repos_only;
664           svn_boolean_t local_only;
665           svn_node_kind_t base_kind;
666
667           if (eb->cancel_func)
668             SVN_ERR(eb->cancel_func(eb->cancel_baton));
669
670           /* In the anchor directory, if the anchor is not the target then all
671              entries other than the target should not be diff'd. Running diff
672              on one file in a directory should not diff other files in that
673              directory. */
674           if (in_anchor_not_target && strcmp(eb->target, name))
675             continue;
676
677           if (compared && svn_hash_gets(compared, name))
678             continue;
679
680           if (NOT_PRESENT(info->status))
681             continue;
682
683           assert(info->status == svn_wc__db_status_normal
684                  || info->status == svn_wc__db_status_added
685                  || info->status == svn_wc__db_status_deleted);
686
687           svn_pool_clear(iterpool);
688           child_abspath = svn_dirent_join(local_abspath, name, iterpool);
689           child_relpath = svn_relpath_join(path, name, iterpool);
690
691           repos_only = FALSE;
692           local_only = FALSE;
693
694           if (!info->have_base)
695             {
696               local_only = TRUE; /* Only report additions */
697
698               if (info->status == svn_wc__db_status_deleted)
699                 continue; /* Nothing added (deleted copy) */
700             }
701           else if (info->status == svn_wc__db_status_normal)
702             {
703               /* Simple diff */
704               base_kind = info->kind;
705             }
706           else if (info->status == svn_wc__db_status_deleted
707                    && (!eb->diff_pristine || !info->have_more_work))
708             {
709               svn_wc__db_status_t base_status;
710               repos_only = TRUE;
711               SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
712                                                NULL, NULL, NULL, NULL, NULL,
713                                                NULL, NULL, NULL, NULL, NULL,
714                                                NULL, NULL, NULL,
715                                                db, child_abspath,
716                                                iterpool, iterpool));
717
718               if (NOT_PRESENT(base_status))
719                 continue;
720             }
721           else
722             {
723               /* working status is either added or deleted */
724               svn_wc__db_status_t base_status;
725
726               SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
727                                                NULL, NULL, NULL, NULL, NULL,
728                                                NULL, NULL, NULL, NULL, NULL,
729                                                NULL, NULL, NULL,
730                                                db, child_abspath,
731                                                iterpool, iterpool));
732
733               if (NOT_PRESENT(base_status))
734                 local_only = TRUE;
735               else if (base_kind != info->kind || !eb->ignore_ancestry)
736                 {
737                   repos_only = TRUE;
738                   local_only = TRUE;
739                 }
740             }
741
742           if (eb->local_before_remote && local_only)
743             {
744               if (info->kind == svn_node_file && diff_files)
745                 SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
746                                                      child_relpath,
747                                                      eb->processor, dir_baton,
748                                                      eb->diff_pristine,
749                                                      eb->cancel_func,
750                                                      eb->cancel_baton,
751                                                      iterpool));
752               else if (info->kind == svn_node_dir && diff_dirs)
753                 SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
754                                                     child_relpath,
755                                                     depth_below_here,
756                                                     eb->processor, dir_baton,
757                                                     eb->diff_pristine,
758                                                     eb->cancel_func,
759                                                     eb->cancel_baton,
760                                                     iterpool));
761             }
762
763           if (repos_only)
764             {
765               /* Report repository form deleted */
766               if (base_kind == svn_node_file && diff_files)
767                 SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath,
768                                                     child_relpath, eb->revnum,
769                                                     eb->processor, dir_baton,
770                                                     iterpool));
771               else if (base_kind == svn_node_dir && diff_dirs)
772                 SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath,
773                                                    child_relpath, eb->revnum,
774                                                    depth_below_here,
775                                                    eb->processor, dir_baton,
776                                                    eb->cancel_func,
777                                                    eb->cancel_baton,
778                                                    iterpool));
779             }
780           else if (!local_only) /* Not local only nor remote only */
781             {
782               /* Diff base against actual */
783               if (info->kind == svn_node_file && diff_files)
784                 {
785                   if (info->status != svn_wc__db_status_normal
786                       || !eb->diff_pristine)
787                     {
788                       SVN_ERR(svn_wc__diff_base_working_diff(
789                                                 db, child_abspath,
790                                                 child_relpath,
791                                                 eb->revnum,
792                                                 eb->processor, dir_baton,
793                                                 eb->diff_pristine,
794                                                 eb->cancel_func,
795                                                 eb->cancel_baton,
796                                                 scratch_pool));
797                     }
798                 }
799               else if (info->kind == svn_node_dir && diff_dirs)
800                 SVN_ERR(walk_local_nodes_diff(eb, child_abspath,
801                                               child_relpath,
802                                               depth_below_here,
803                                               NULL /* compared */,
804                                               dir_baton,
805                                               scratch_pool));
806             }
807
808           if (!eb->local_before_remote && local_only)
809             {
810               if (info->kind == svn_node_file && diff_files)
811                 SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
812                                                      child_relpath,
813                                                      eb->processor, dir_baton,
814                                                      eb->diff_pristine,
815                                                      eb->cancel_func,
816                                                      eb->cancel_baton,
817                                                      iterpool));
818               else if (info->kind == svn_node_dir && diff_dirs)
819                 SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
820                                                      child_relpath, depth_below_here,
821                                                      eb->processor, dir_baton,
822                                                      eb->diff_pristine,
823                                                      eb->cancel_func,
824                                                      eb->cancel_baton,
825                                                      iterpool));
826             }
827         }
828     }
829
830   if (compared)
831     return SVN_NO_ERROR;
832
833   /* Check for local property mods on this directory, if we haven't
834      already reported them. */
835   if (! skip
836       && ! in_anchor_not_target
837       && props_mod)
838     {
839       apr_array_header_t *propchanges;
840       apr_hash_t *left_props;
841       apr_hash_t *right_props;
842
843       SVN_ERR(svn_wc__internal_propdiff(&propchanges, &left_props,
844                                         db, local_abspath,
845                                         scratch_pool, scratch_pool));
846
847       right_props = svn_prop__patch(left_props, propchanges, scratch_pool);
848
849       SVN_ERR(eb->processor->dir_changed(path,
850                                          left_src,
851                                          right_src,
852                                          left_props,
853                                          right_props,
854                                          propchanges,
855                                          dir_baton,
856                                          eb->processor,
857                                          scratch_pool));
858     }
859   else if (! skip)
860     SVN_ERR(eb->processor->dir_closed(path,
861                                       left_src,
862                                       right_src,
863                                       dir_baton,
864                                       eb->processor,
865                                       scratch_pool));
866
867   svn_pool_destroy(iterpool);
868
869   return SVN_NO_ERROR;
870 }
871
872 svn_error_t *
873 svn_wc__diff_local_only_file(svn_wc__db_t *db,
874                              const char *local_abspath,
875                              const char *relpath,
876                              const svn_diff_tree_processor_t *processor,
877                              void *processor_parent_baton,
878                              svn_boolean_t diff_pristine,
879                              svn_cancel_func_t cancel_func,
880                              void *cancel_baton,
881                              apr_pool_t *scratch_pool)
882 {
883   svn_diff_source_t *right_src;
884   svn_diff_source_t *copyfrom_src = NULL;
885   svn_wc__db_status_t status;
886   svn_node_kind_t kind;
887   const svn_checksum_t *checksum;
888   const char *original_repos_relpath;
889   svn_revnum_t original_revision;
890   svn_boolean_t had_props;
891   svn_boolean_t props_mod;
892   apr_hash_t *pristine_props;
893   apr_hash_t *right_props = NULL;
894   const char *pristine_file;
895   const char *translated_file;
896   svn_revnum_t revision;
897   void *file_baton = NULL;
898   svn_boolean_t skip = FALSE;
899   svn_boolean_t file_mod = TRUE;
900
901   SVN_ERR(svn_wc__db_read_info(&status, &kind, &revision, NULL, NULL, NULL,
902                                NULL, NULL, NULL, NULL, &checksum, NULL,
903                                &original_repos_relpath, NULL, NULL,
904                                &original_revision, NULL, NULL, NULL,
905                                NULL, NULL, NULL, &had_props,
906                                &props_mod, NULL, NULL, NULL,
907                                db, local_abspath,
908                                scratch_pool, scratch_pool));
909
910   assert(kind == svn_node_file
911          && (status == svn_wc__db_status_normal
912              || status == svn_wc__db_status_added
913              || (status == svn_wc__db_status_deleted && diff_pristine)));
914
915
916   if (status == svn_wc__db_status_deleted)
917     {
918       assert(diff_pristine);
919
920       SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL,
921                                             NULL, &checksum, NULL, &had_props,
922                                             &pristine_props,
923                                             db, local_abspath,
924                                             scratch_pool, scratch_pool));
925       props_mod = FALSE;
926     }
927   else if (!had_props)
928     pristine_props = apr_hash_make(scratch_pool);
929   else
930     SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props,
931                                            db, local_abspath,
932                                            scratch_pool, scratch_pool));
933
934   if (original_repos_relpath)
935     {
936       copyfrom_src = svn_diff__source_create(original_revision, scratch_pool);
937       copyfrom_src->repos_relpath = original_repos_relpath;
938     }
939
940   if (props_mod || !SVN_IS_VALID_REVNUM(revision))
941     right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
942   else
943     {
944       if (diff_pristine)
945         file_mod = FALSE;
946       else
947         SVN_ERR(svn_wc__internal_file_modified_p(&file_mod, db, local_abspath,
948                                                  FALSE, scratch_pool));
949
950       if (!file_mod)
951         right_src = svn_diff__source_create(revision, scratch_pool);
952       else
953         right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
954     }
955
956   SVN_ERR(processor->file_opened(&file_baton, &skip,
957                                  relpath,
958                                  NULL /* left_source */,
959                                  right_src,
960                                  copyfrom_src,
961                                  processor_parent_baton,
962                                  processor,
963                                  scratch_pool, scratch_pool));
964
965   if (skip)
966     return SVN_NO_ERROR;
967
968   if (props_mod && !diff_pristine)
969     SVN_ERR(svn_wc__db_read_props(&right_props, db, local_abspath,
970                                   scratch_pool, scratch_pool));
971   else
972     right_props = svn_prop_hash_dup(pristine_props, scratch_pool);
973
974   if (checksum)
975     SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, db, local_abspath,
976                                          checksum, scratch_pool, scratch_pool));
977   else
978     pristine_file = NULL;
979
980   if (diff_pristine)
981     {
982       translated_file = pristine_file; /* No translation needed */
983     }
984   else
985     {
986       SVN_ERR(svn_wc__internal_translated_file(
987            &translated_file, local_abspath, db, local_abspath,
988            SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP,
989            cancel_func, cancel_baton,
990            scratch_pool, scratch_pool));
991     }
992
993   SVN_ERR(processor->file_added(relpath,
994                                 copyfrom_src,
995                                 right_src,
996                                 copyfrom_src
997                                   ? pristine_file
998                                   : NULL,
999                                 translated_file,
1000                                 copyfrom_src
1001                                   ? pristine_props
1002                                   : NULL,
1003                                 right_props,
1004                                 file_baton,
1005                                 processor,
1006                                 scratch_pool));
1007
1008   return SVN_NO_ERROR;
1009 }
1010
1011 svn_error_t *
1012 svn_wc__diff_local_only_dir(svn_wc__db_t *db,
1013                             const char *local_abspath,
1014                             const char *relpath,
1015                             svn_depth_t depth,
1016                             const svn_diff_tree_processor_t *processor,
1017                             void *processor_parent_baton,
1018                             svn_boolean_t diff_pristine,
1019                             svn_cancel_func_t cancel_func,
1020                             void *cancel_baton,
1021                             apr_pool_t *scratch_pool)
1022 {
1023   svn_wc__db_status_t status;
1024   svn_node_kind_t kind;
1025   svn_boolean_t had_props;
1026   svn_boolean_t props_mod;
1027   const char *original_repos_relpath;
1028   svn_revnum_t original_revision;
1029   svn_diff_source_t *copyfrom_src = NULL;
1030   apr_hash_t *pristine_props;
1031   const apr_array_header_t *children;
1032   int i;
1033   apr_pool_t *iterpool;
1034   void *pdb = NULL;
1035   svn_boolean_t skip = FALSE;
1036   svn_boolean_t skip_children = FALSE;
1037   svn_diff_source_t *right_src = svn_diff__source_create(SVN_INVALID_REVNUM,
1038                                                          scratch_pool);
1039
1040   SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL,
1041                                NULL, NULL, NULL, NULL, NULL, NULL,
1042                                &original_repos_relpath, NULL, NULL,
1043                                &original_revision, NULL, NULL, NULL,
1044                                NULL, NULL, NULL, &had_props,
1045                                &props_mod, NULL, NULL, NULL,
1046                                db, local_abspath,
1047                                scratch_pool, scratch_pool));
1048   if (original_repos_relpath)
1049     {
1050       copyfrom_src = svn_diff__source_create(original_revision, scratch_pool);
1051       copyfrom_src->repos_relpath = original_repos_relpath;
1052     }
1053
1054   /* svn_wc__db_status_incomplete should never happen, as the result won't be
1055      stable or guaranteed related to what is in the repository for this
1056      revision, but without this it would be hard to diagnose that status... */
1057   assert(kind == svn_node_dir
1058          && (status == svn_wc__db_status_normal
1059              || status == svn_wc__db_status_incomplete
1060              || status == svn_wc__db_status_added
1061              || (status == svn_wc__db_status_deleted && diff_pristine)));
1062
1063   if (status == svn_wc__db_status_deleted)
1064     {
1065       assert(diff_pristine);
1066
1067       SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL,
1068                                             NULL, NULL, NULL, &had_props,
1069                                             &pristine_props,
1070                                             db, local_abspath,
1071                                             scratch_pool, scratch_pool));
1072       props_mod = FALSE;
1073     }
1074   else if (!had_props)
1075     pristine_props = apr_hash_make(scratch_pool);
1076   else
1077     SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props,
1078                                            db, local_abspath,
1079                                            scratch_pool, scratch_pool));
1080
1081   /* Report the addition of the directory's contents. */
1082   iterpool = svn_pool_create(scratch_pool);
1083
1084   SVN_ERR(processor->dir_opened(&pdb, &skip, &skip_children,
1085                                 relpath,
1086                                 NULL,
1087                                 right_src,
1088                                 copyfrom_src,
1089                                 processor_parent_baton,
1090                                 processor,
1091                                 scratch_pool, iterpool));
1092
1093   if ((depth > svn_depth_empty || depth == svn_depth_unknown)
1094       && ! skip_children)
1095     {
1096       svn_depth_t depth_below_here = depth;
1097       apr_hash_t *nodes;
1098       apr_hash_t *conflicts;
1099
1100       if (depth_below_here == svn_depth_immediates)
1101         depth_below_here = svn_depth_empty;
1102
1103       SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
1104                                             db, local_abspath,
1105                                             FALSE /* base_tree_only */,
1106                                             scratch_pool, iterpool));
1107
1108
1109       children = svn_sort__hash(nodes, svn_sort_compare_items_lexically,
1110                                 scratch_pool);
1111
1112       for (i = 0; i < children->nelts; i++)
1113         {
1114           svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, svn_sort__item_t);
1115           const char *name = item->key;
1116           struct svn_wc__db_info_t *info = item->value;
1117           const char *child_abspath;
1118           const char *child_relpath;
1119
1120           svn_pool_clear(iterpool);
1121
1122           if (cancel_func)
1123             SVN_ERR(cancel_func(cancel_baton));
1124
1125           child_abspath = svn_dirent_join(local_abspath, name, iterpool);
1126
1127           if (NOT_PRESENT(info->status))
1128             {
1129               continue;
1130             }
1131
1132           /* If comparing against WORKING, skip entries that are
1133              schedule-deleted - they don't really exist. */
1134           if (!diff_pristine && info->status == svn_wc__db_status_deleted)
1135             continue;
1136
1137           child_relpath = svn_relpath_join(relpath, name, iterpool);
1138
1139           switch (info->kind)
1140             {
1141             case svn_node_file:
1142             case svn_node_symlink:
1143               SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
1144                                                    child_relpath,
1145                                                    processor, pdb,
1146                                                    diff_pristine,
1147                                                    cancel_func, cancel_baton,
1148                                                    scratch_pool));
1149               break;
1150
1151             case svn_node_dir:
1152               if (depth > svn_depth_files || depth == svn_depth_unknown)
1153                 {
1154                   SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
1155                                                       child_relpath,
1156                                                       depth_below_here,
1157                                                       processor, pdb,
1158                                                       diff_pristine,
1159                                                       cancel_func,
1160                                                       cancel_baton,
1161                                                       iterpool));
1162                 }
1163               break;
1164
1165             default:
1166               break;
1167             }
1168         }
1169     }
1170
1171   if (!skip)
1172     {
1173       apr_hash_t *right_props;
1174
1175       if (props_mod && !diff_pristine)
1176         SVN_ERR(svn_wc__db_read_props(&right_props, db, local_abspath,
1177                                       scratch_pool, scratch_pool));
1178       else
1179         right_props = svn_prop_hash_dup(pristine_props, scratch_pool);
1180
1181       SVN_ERR(processor->dir_added(relpath,
1182                                    copyfrom_src,
1183                                    right_src,
1184                                    copyfrom_src
1185                                      ? pristine_props
1186                                      : NULL,
1187                                    right_props,
1188                                    pdb,
1189                                    processor,
1190                                    iterpool));
1191     }
1192   svn_pool_destroy(iterpool);
1193
1194   return SVN_NO_ERROR;
1195 }
1196
1197 /* Reports local changes. */
1198 static svn_error_t *
1199 handle_local_only(struct dir_baton_t *pb,
1200                   const char *name,
1201                   apr_pool_t *scratch_pool)
1202 {
1203   struct edit_baton_t *eb = pb->eb;
1204   const struct svn_wc__db_info_t *info;
1205   svn_boolean_t repos_delete = (pb->deletes
1206                                 && svn_hash_gets(pb->deletes, name));
1207
1208   assert(!strchr(name, '/'));
1209   assert(!pb->added || eb->ignore_ancestry);
1210
1211   if (pb->skip_children)
1212     return SVN_NO_ERROR;
1213
1214   SVN_ERR(ensure_local_info(pb, scratch_pool));
1215
1216   info = svn_hash_gets(pb->local_info, name);
1217
1218   if (info == NULL || NOT_PRESENT(info->status))
1219     return SVN_NO_ERROR;
1220
1221   switch (info->status)
1222     {
1223       case svn_wc__db_status_incomplete:
1224         return SVN_NO_ERROR; /* Not local only */
1225
1226       case svn_wc__db_status_normal:
1227         if (!repos_delete)
1228           return SVN_NO_ERROR; /* Local and remote */
1229         svn_hash_sets(pb->deletes, name, NULL);
1230         break;
1231
1232       case svn_wc__db_status_deleted:
1233         if (!(eb->diff_pristine && repos_delete))
1234           return SVN_NO_ERROR;
1235         break;
1236
1237       case svn_wc__db_status_added:
1238       default:
1239         break;
1240     }
1241
1242   if (info->kind == svn_node_dir)
1243     {
1244       svn_depth_t depth ;
1245
1246       if (pb->depth == svn_depth_infinity || pb->depth == svn_depth_unknown)
1247         depth = pb->depth;
1248       else
1249         depth = svn_depth_empty;
1250
1251       SVN_ERR(svn_wc__diff_local_only_dir(
1252                       eb->db,
1253                       svn_dirent_join(pb->local_abspath, name, scratch_pool),
1254                       svn_relpath_join(pb->relpath, name, scratch_pool),
1255                       repos_delete ? svn_depth_infinity : depth,
1256                       eb->processor, pb->pdb,
1257                       eb->diff_pristine,
1258                       eb->cancel_func, eb->cancel_baton,
1259                       scratch_pool));
1260     }
1261   else
1262     SVN_ERR(svn_wc__diff_local_only_file(
1263                       eb->db,
1264                       svn_dirent_join(pb->local_abspath, name, scratch_pool),
1265                       svn_relpath_join(pb->relpath, name, scratch_pool),
1266                       eb->processor, pb->pdb,
1267                       eb->diff_pristine,
1268                       eb->cancel_func, eb->cancel_baton,
1269                       scratch_pool));
1270
1271   return SVN_NO_ERROR;
1272 }
1273
1274 /* Reports a file LOCAL_ABSPATH in BASE as deleted */
1275 svn_error_t *
1276 svn_wc__diff_base_only_file(svn_wc__db_t *db,
1277                             const char *local_abspath,
1278                             const char *relpath,
1279                             svn_revnum_t revision,
1280                             const svn_diff_tree_processor_t *processor,
1281                             void *processor_parent_baton,
1282                             apr_pool_t *scratch_pool)
1283 {
1284   svn_wc__db_status_t status;
1285   svn_node_kind_t kind;
1286   const svn_checksum_t *checksum;
1287   apr_hash_t *props;
1288   void *file_baton = NULL;
1289   svn_boolean_t skip = FALSE;
1290   svn_diff_source_t *left_src;
1291   const char *pristine_file;
1292
1293   SVN_ERR(svn_wc__db_base_get_info(&status, &kind,
1294                                    SVN_IS_VALID_REVNUM(revision)
1295                                         ? NULL : &revision,
1296                                    NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1297                                    &checksum, NULL, NULL, NULL, &props, NULL,
1298                                    db, local_abspath,
1299                                    scratch_pool, scratch_pool));
1300
1301   SVN_ERR_ASSERT(status == svn_wc__db_status_normal
1302                  && kind == svn_node_file
1303                  && checksum);
1304
1305   left_src = svn_diff__source_create(revision, scratch_pool);
1306
1307   SVN_ERR(processor->file_opened(&file_baton, &skip,
1308                                  relpath,
1309                                  left_src,
1310                                  NULL /* right_src */,
1311                                  NULL /* copyfrom_source */,
1312                                  processor_parent_baton,
1313                                  processor,
1314                                  scratch_pool, scratch_pool));
1315
1316   if (skip)
1317     return SVN_NO_ERROR;
1318
1319   SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file,
1320                                        db, local_abspath, checksum,
1321                                        scratch_pool, scratch_pool));
1322
1323   SVN_ERR(processor->file_deleted(relpath,
1324                                   left_src,
1325                                   pristine_file,
1326                                   props,
1327                                   file_baton,
1328                                   processor,
1329                                   scratch_pool));
1330
1331   return SVN_NO_ERROR;
1332 }
1333
1334 svn_error_t *
1335 svn_wc__diff_base_only_dir(svn_wc__db_t *db,
1336                            const char *local_abspath,
1337                            const char *relpath,
1338                            svn_revnum_t revision,
1339                            svn_depth_t depth,
1340                            const svn_diff_tree_processor_t *processor,
1341                            void *processor_parent_baton,
1342                            svn_cancel_func_t cancel_func,
1343                            void *cancel_baton,
1344                            apr_pool_t *scratch_pool)
1345 {
1346   void *dir_baton = NULL;
1347   svn_boolean_t skip = FALSE;
1348   svn_boolean_t skip_children = FALSE;
1349   svn_diff_source_t *left_src;
1350   svn_revnum_t report_rev = revision;
1351
1352   if (!SVN_IS_VALID_REVNUM(report_rev))
1353     SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &report_rev, NULL, NULL, NULL,
1354                                      NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1355                                      NULL, NULL, NULL,
1356                                      db, local_abspath,
1357                                      scratch_pool, scratch_pool));
1358
1359   left_src = svn_diff__source_create(report_rev, scratch_pool);
1360
1361   SVN_ERR(processor->dir_opened(&dir_baton, &skip, &skip_children,
1362                                 relpath,
1363                                 left_src,
1364                                 NULL /* right_src */,
1365                                 NULL /* copyfrom_src */,
1366                                 processor_parent_baton,
1367                                 processor,
1368                                 scratch_pool, scratch_pool));
1369
1370   if (!skip_children && (depth == svn_depth_unknown || depth > svn_depth_empty))
1371     {
1372       apr_hash_t *nodes;
1373       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1374       apr_array_header_t *children;
1375       int i;
1376
1377       SVN_ERR(svn_wc__db_base_get_children_info(&nodes, db, local_abspath,
1378                                                 scratch_pool, iterpool));
1379
1380       children = svn_sort__hash(nodes, svn_sort_compare_items_lexically,
1381                                 scratch_pool);
1382
1383       for (i = 0; i < children->nelts; i++)
1384         {
1385           svn_sort__item_t *item = &APR_ARRAY_IDX(children, i,
1386                                                   svn_sort__item_t);
1387           const char *name = item->key;
1388           struct svn_wc__db_base_info_t *info = item->value;
1389           const char *child_abspath;
1390           const char *child_relpath;
1391
1392           if (info->status != svn_wc__db_status_normal)
1393             continue;
1394
1395           if (cancel_func)
1396             SVN_ERR(cancel_func(cancel_baton));
1397
1398           svn_pool_clear(iterpool);
1399
1400           child_abspath = svn_dirent_join(local_abspath, name, iterpool);
1401           child_relpath = svn_relpath_join(relpath, name, iterpool);
1402
1403           switch (info->kind)
1404             {
1405               case svn_node_file:
1406               case svn_node_symlink:
1407                 SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath,
1408                                                     child_relpath,
1409                                                     revision,
1410                                                     processor, dir_baton,
1411                                                     iterpool));
1412                 break;
1413               case svn_node_dir:
1414                 if (depth > svn_depth_files || depth == svn_depth_unknown)
1415                   {
1416                     svn_depth_t depth_below_here = depth;
1417
1418                     if (depth_below_here == svn_depth_immediates)
1419                       depth_below_here = svn_depth_empty;
1420
1421                     SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath,
1422                                                        child_relpath,
1423                                                        revision,
1424                                                        depth_below_here,
1425                                                        processor, dir_baton,
1426                                                        cancel_func,
1427                                                        cancel_baton,
1428                                                        iterpool));
1429                   }
1430                 break;
1431
1432               default:
1433                 break;
1434             }
1435         }
1436     }
1437
1438   if (!skip)
1439     {
1440       apr_hash_t *props;
1441       SVN_ERR(svn_wc__db_base_get_props(&props, db, local_abspath,
1442                                         scratch_pool, scratch_pool));
1443
1444       SVN_ERR(processor->dir_deleted(relpath,
1445                                      left_src,
1446                                      props,
1447                                      dir_baton,
1448                                      processor,
1449                                      scratch_pool));
1450     }
1451
1452   return SVN_NO_ERROR;
1453 }
1454 \f
1455 /* An svn_delta_editor_t function. */
1456 static svn_error_t *
1457 set_target_revision(void *edit_baton,
1458                     svn_revnum_t target_revision,
1459                     apr_pool_t *pool)
1460 {
1461   struct edit_baton_t *eb = edit_baton;
1462   eb->revnum = target_revision;
1463
1464   return SVN_NO_ERROR;
1465 }
1466
1467 /* An svn_delta_editor_t function. The root of the comparison hierarchy */
1468 static svn_error_t *
1469 open_root(void *edit_baton,
1470           svn_revnum_t base_revision,
1471           apr_pool_t *dir_pool,
1472           void **root_baton)
1473 {
1474   struct edit_baton_t *eb = edit_baton;
1475   struct dir_baton_t *db;
1476
1477   eb->root_opened = TRUE;
1478   db = make_dir_baton("", NULL, eb, FALSE, eb->depth, dir_pool);
1479   *root_baton = db;
1480
1481   if (eb->target[0] == '\0')
1482     {
1483       db->left_src = svn_diff__source_create(eb->revnum, db->pool);
1484       db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool);
1485
1486       SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip,
1487                                         &db->skip_children,
1488                                         "",
1489                                         db->left_src,
1490                                         db->right_src,
1491                                         NULL /* copyfrom_source */,
1492                                         NULL /* parent_baton */,
1493                                         eb->processor,
1494                                         db->pool, db->pool));
1495     }
1496   else
1497     db->skip = TRUE; /* Skip this, but not the children */
1498
1499   return SVN_NO_ERROR;
1500 }
1501
1502 /* An svn_delta_editor_t function. */
1503 static svn_error_t *
1504 delete_entry(const char *path,
1505              svn_revnum_t base_revision,
1506              void *parent_baton,
1507              apr_pool_t *pool)
1508 {
1509   struct dir_baton_t *pb = parent_baton;
1510   const char *name = svn_dirent_basename(path, pb->pool);
1511
1512   if (!pb->deletes)
1513     pb->deletes = apr_hash_make(pb->pool);
1514
1515   svn_hash_sets(pb->deletes, name, "");
1516   return SVN_NO_ERROR;
1517 }
1518
1519 /* An svn_delta_editor_t function. */
1520 static svn_error_t *
1521 add_directory(const char *path,
1522               void *parent_baton,
1523               const char *copyfrom_path,
1524               svn_revnum_t copyfrom_revision,
1525               apr_pool_t *dir_pool,
1526               void **child_baton)
1527 {
1528   struct dir_baton_t *pb = parent_baton;
1529   struct edit_baton_t *eb = pb->eb;
1530   struct dir_baton_t *db;
1531   svn_depth_t subdir_depth = (pb->depth == svn_depth_immediates)
1532                               ? svn_depth_empty : pb->depth;
1533
1534   db = make_dir_baton(path, pb, pb->eb, TRUE, subdir_depth,
1535                       dir_pool);
1536   *child_baton = db;
1537
1538   if (pb->repos_only || !eb->ignore_ancestry)
1539     db->repos_only = TRUE;
1540   else
1541     {
1542       struct svn_wc__db_info_t *info;
1543       SVN_ERR(ensure_local_info(pb, dir_pool));
1544
1545       info = svn_hash_gets(pb->local_info, db->name);
1546
1547       if (!info || info->kind != svn_node_dir || NOT_PRESENT(info->status))
1548         db->repos_only = TRUE;
1549
1550       if (!db->repos_only && info->status != svn_wc__db_status_added)
1551         db->repos_only = TRUE;
1552
1553       if (!db->repos_only)
1554         {
1555           db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool);
1556           db->ignoring_ancestry = TRUE;
1557
1558           svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, db->name), "");
1559         }
1560     }
1561
1562   db->left_src = svn_diff__source_create(eb->revnum, db->pool);
1563
1564   if (eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry)
1565     SVN_ERR(handle_local_only(pb, db->name, dir_pool));
1566
1567   SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, &db->skip_children,
1568                                     db->relpath,
1569                                     db->left_src,
1570                                     db->right_src,
1571                                     NULL /* copyfrom src */,
1572                                     pb->pdb,
1573                                     eb->processor,
1574                                     db->pool, db->pool));
1575
1576   return SVN_NO_ERROR;
1577 }
1578
1579 /* An svn_delta_editor_t function. */
1580 static svn_error_t *
1581 open_directory(const char *path,
1582                void *parent_baton,
1583                svn_revnum_t base_revision,
1584                apr_pool_t *dir_pool,
1585                void **child_baton)
1586 {
1587   struct dir_baton_t *pb = parent_baton;
1588   struct edit_baton_t *eb = pb->eb;
1589   struct dir_baton_t *db;
1590   svn_depth_t subdir_depth = (pb->depth == svn_depth_immediates)
1591                               ? svn_depth_empty : pb->depth;
1592
1593   /* Allocate path from the parent pool since the memory is used in the
1594      parent's compared hash */
1595   db = make_dir_baton(path, pb, pb->eb, FALSE, subdir_depth, dir_pool);
1596   *child_baton = db;
1597
1598   if (pb->repos_only)
1599     db->repos_only = TRUE;
1600   else
1601     {
1602       struct svn_wc__db_info_t *info;
1603       SVN_ERR(ensure_local_info(pb, dir_pool));
1604
1605       info = svn_hash_gets(pb->local_info, db->name);
1606
1607       if (!info || info->kind != svn_node_dir || NOT_PRESENT(info->status))
1608         db->repos_only = TRUE;
1609
1610       if (!db->repos_only)
1611         switch (info->status)
1612           {
1613             case svn_wc__db_status_normal:
1614               break;
1615             case svn_wc__db_status_deleted:
1616               db->repos_only = TRUE;
1617
1618               if (!info->have_more_work)
1619                 svn_hash_sets(pb->compared,
1620                               apr_pstrdup(pb->pool, db->name), "");
1621               break;
1622             case svn_wc__db_status_added:
1623               if (eb->ignore_ancestry)
1624                 db->ignoring_ancestry = TRUE;
1625               else
1626                 db->repos_only = TRUE;
1627               break;
1628             default:
1629               SVN_ERR_MALFUNCTION();
1630         }
1631
1632       if (!db->repos_only)
1633         {
1634           db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool);
1635           svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, db->name), "");
1636         }
1637     }
1638
1639   db->left_src = svn_diff__source_create(eb->revnum, db->pool);
1640
1641   if (eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry)
1642     SVN_ERR(handle_local_only(pb, db->name, dir_pool));
1643
1644   SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, &db->skip_children,
1645                                     db->relpath,
1646                                     db->left_src,
1647                                     db->right_src,
1648                                     NULL /* copyfrom src */,
1649                                     pb->pdb,
1650                                     eb->processor,
1651                                     db->pool, db->pool));
1652
1653   return SVN_NO_ERROR;
1654 }
1655
1656
1657 /* An svn_delta_editor_t function.  When a directory is closed, all the
1658  * directory elements that have been added or replaced will already have been
1659  * diff'd. However there may be other elements in the working copy
1660  * that have not yet been considered.  */
1661 static svn_error_t *
1662 close_directory(void *dir_baton,
1663                 apr_pool_t *pool)
1664 {
1665   struct dir_baton_t *db = dir_baton;
1666   struct dir_baton_t *pb = db->parent_baton;
1667   struct edit_baton_t *eb = db->eb;
1668   apr_pool_t *scratch_pool = db->pool;
1669   svn_boolean_t reported_closed = FALSE;
1670
1671   if (!db->skip_children && db->deletes && apr_hash_count(db->deletes))
1672     {
1673       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1674       apr_array_header_t *children;
1675       int i;
1676       children = svn_sort__hash(db->deletes, svn_sort_compare_items_lexically,
1677                                 scratch_pool);
1678
1679       for (i = 0; i < children->nelts; i++)
1680         {
1681           svn_sort__item_t *item = &APR_ARRAY_IDX(children, i,
1682                                                   svn_sort__item_t);
1683           const char *name = item->key;
1684
1685           svn_pool_clear(iterpool);
1686           SVN_ERR(handle_local_only(db, name, iterpool));
1687
1688           svn_hash_sets(db->compared, name, "");
1689         }
1690
1691       svn_pool_destroy(iterpool);
1692     }
1693
1694   /* Report local modifications for this directory.  Skip added
1695      directories since they can only contain added elements, all of
1696      which have already been diff'd. */
1697   if (!db->repos_only && !db->skip_children)
1698   {
1699     SVN_ERR(walk_local_nodes_diff(eb,
1700                                   db->local_abspath,
1701                                   db->relpath,
1702                                   db->depth,
1703                                   db->compared,
1704                                   db->pdb,
1705                                   scratch_pool));
1706   }
1707
1708   /* Report the property changes on the directory itself, if necessary. */
1709   if (db->skip)
1710     {
1711       /* Diff processor requested no directory details */
1712     }
1713   else if (db->propchanges->nelts > 0 || db->repos_only)
1714     {
1715       apr_hash_t *repos_props;
1716
1717       if (db->added)
1718         {
1719           repos_props = apr_hash_make(scratch_pool);
1720         }
1721       else
1722         {
1723           SVN_ERR(svn_wc__db_base_get_props(&repos_props,
1724                                             eb->db, db->local_abspath,
1725                                             scratch_pool, scratch_pool));
1726         }
1727
1728       /* Add received property changes and entry props */
1729       if (db->propchanges->nelts)
1730         repos_props = svn_prop__patch(repos_props, db->propchanges,
1731                                       scratch_pool);
1732
1733       if (db->repos_only)
1734         {
1735           SVN_ERR(eb->processor->dir_deleted(db->relpath,
1736                                              db->left_src,
1737                                              repos_props,
1738                                              db->pdb,
1739                                              eb->processor,
1740                                              scratch_pool));
1741           reported_closed = TRUE;
1742         }
1743       else
1744         {
1745           apr_hash_t *local_props;
1746           apr_array_header_t *prop_changes;
1747
1748           if (eb->diff_pristine)
1749             SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL,
1750                                                   NULL, NULL, NULL, NULL,
1751                                                   &local_props,
1752                                                   eb->db, db->local_abspath,
1753                                                   scratch_pool, scratch_pool));
1754           else
1755             SVN_ERR(svn_wc__db_read_props(&local_props,
1756                                           eb->db, db->local_abspath,
1757                                           scratch_pool, scratch_pool));
1758
1759           SVN_ERR(svn_prop_diffs(&prop_changes, local_props, repos_props,
1760                                  scratch_pool));
1761
1762           /* ### as a good diff processor we should now only report changes
1763                  if there are non-entry changes, but for now we stick to
1764                  compatibility */
1765
1766           if (prop_changes->nelts)
1767             {
1768               SVN_ERR(eb->processor->dir_changed(db->relpath,
1769                                                  db->left_src,
1770                                                  db->right_src,
1771                                                  repos_props,
1772                                                  local_props,
1773                                                  prop_changes,
1774                                                  db->pdb,
1775                                                  eb->processor,
1776                                                  scratch_pool));
1777               reported_closed = TRUE;
1778           }
1779         }
1780     }
1781
1782   /* Mark this directory as compared in the parent directory's baton,
1783      unless this is the root of the comparison. */
1784   if (!reported_closed && !db->skip)
1785     SVN_ERR(eb->processor->dir_closed(db->relpath,
1786                                       db->left_src,
1787                                       db->right_src,
1788                                       db->pdb,
1789                                       eb->processor,
1790                                       scratch_pool));
1791
1792   if (pb && !eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry)
1793     SVN_ERR(handle_local_only(pb, db->name, scratch_pool));
1794
1795   SVN_ERR(maybe_done(db)); /* destroys scratch_pool */
1796
1797   return SVN_NO_ERROR;
1798 }
1799
1800 /* An svn_delta_editor_t function. */
1801 static svn_error_t *
1802 add_file(const char *path,
1803          void *parent_baton,
1804          const char *copyfrom_path,
1805          svn_revnum_t copyfrom_revision,
1806          apr_pool_t *file_pool,
1807          void **file_baton)
1808 {
1809   struct dir_baton_t *pb = parent_baton;
1810   struct edit_baton_t *eb = pb->eb;
1811   struct file_baton_t *fb;
1812
1813   fb = make_file_baton(path, TRUE, pb, file_pool);
1814   *file_baton = fb;
1815
1816   if (pb->skip_children)
1817     {
1818       fb->skip = TRUE;
1819       return SVN_NO_ERROR;
1820     }
1821   else if (pb->repos_only || !eb->ignore_ancestry)
1822     fb->repos_only = TRUE;
1823   else
1824     {
1825       struct svn_wc__db_info_t *info;
1826       SVN_ERR(ensure_local_info(pb, file_pool));
1827
1828       info = svn_hash_gets(pb->local_info, fb->name);
1829
1830       if (!info || info->kind != svn_node_file || NOT_PRESENT(info->status))
1831         fb->repos_only = TRUE;
1832
1833       if (!fb->repos_only && info->status != svn_wc__db_status_added)
1834         fb->repos_only = TRUE;
1835
1836       if (!fb->repos_only)
1837         {
1838           /* Add this path to the parent directory's list of elements that
1839              have been compared. */
1840           fb->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, fb->pool);
1841           fb->ignoring_ancestry = TRUE;
1842
1843           svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, fb->name), "");
1844         }
1845     }
1846
1847   fb->left_src = svn_diff__source_create(eb->revnum, fb->pool);
1848
1849   SVN_ERR(eb->processor->file_opened(&fb->pfb, &fb->skip,
1850                                      fb->relpath,
1851                                      fb->left_src,
1852                                      fb->right_src,
1853                                      NULL /* copyfrom src */,
1854                                      pb->pdb,
1855                                      eb->processor,
1856                                      fb->pool, fb->pool));
1857
1858   return SVN_NO_ERROR;
1859 }
1860
1861 /* An svn_delta_editor_t function. */
1862 static svn_error_t *
1863 open_file(const char *path,
1864           void *parent_baton,
1865           svn_revnum_t base_revision,
1866           apr_pool_t *file_pool,
1867           void **file_baton)
1868 {
1869   struct dir_baton_t *pb = parent_baton;
1870   struct edit_baton_t *eb = pb->eb;
1871   struct file_baton_t *fb;
1872
1873   fb = make_file_baton(path, FALSE, pb, file_pool);
1874   *file_baton = fb;
1875
1876   if (pb->skip_children)
1877     fb->skip = TRUE;
1878   else if (pb->repos_only)
1879     fb->repos_only = TRUE;
1880   else
1881     {
1882       struct svn_wc__db_info_t *info;
1883       SVN_ERR(ensure_local_info(pb, file_pool));
1884
1885       info = svn_hash_gets(pb->local_info, fb->name);
1886
1887       if (!info || info->kind != svn_node_file || NOT_PRESENT(info->status))
1888         fb->repos_only = TRUE;
1889
1890       if (!fb->repos_only)
1891         switch (info->status)
1892           {
1893             case svn_wc__db_status_normal:
1894               break;
1895             case svn_wc__db_status_deleted:
1896               fb->repos_only = TRUE;
1897               if (!info->have_more_work)
1898                 svn_hash_sets(pb->compared,
1899                               apr_pstrdup(pb->pool, fb->name), "");
1900               break;
1901             case svn_wc__db_status_added:
1902               if (eb->ignore_ancestry)
1903                 fb->ignoring_ancestry = TRUE;
1904               else
1905                 fb->repos_only = TRUE;
1906               break;
1907             default:
1908               SVN_ERR_MALFUNCTION();
1909         }
1910
1911       if (!fb->repos_only)
1912         {
1913           /* Add this path to the parent directory's list of elements that
1914              have been compared. */
1915           fb->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, fb->pool);
1916           svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, fb->name), "");
1917         }
1918     }
1919
1920   fb->left_src = svn_diff__source_create(eb->revnum, fb->pool);
1921
1922   SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1923                                    NULL, NULL, NULL, &fb->base_checksum, NULL,
1924                                    NULL, NULL, &fb->base_props, NULL,
1925                                    eb->db, fb->local_abspath,
1926                                    fb->pool, fb->pool));
1927
1928   SVN_ERR(eb->processor->file_opened(&fb->pfb, &fb->skip,
1929                                      fb->relpath,
1930                                      fb->left_src,
1931                                      fb->right_src,
1932                                      NULL /* copyfrom src */,
1933                                      pb->pdb,
1934                                      eb->processor,
1935                                      fb->pool, fb->pool));
1936
1937   return SVN_NO_ERROR;
1938 }
1939
1940 /* An svn_delta_editor_t function. */
1941 static svn_error_t *
1942 apply_textdelta(void *file_baton,
1943                 const char *base_checksum_hex,
1944                 apr_pool_t *pool,
1945                 svn_txdelta_window_handler_t *handler,
1946                 void **handler_baton)
1947 {
1948   struct file_baton_t *fb = file_baton;
1949   struct edit_baton_t *eb = fb->eb;
1950   svn_stream_t *source;
1951   svn_stream_t *temp_stream;
1952   svn_checksum_t *repos_checksum = NULL;
1953
1954   if (fb->skip)
1955     {
1956       *handler = svn_delta_noop_window_handler;
1957       *handler_baton = NULL;
1958       return SVN_NO_ERROR;
1959     }
1960
1961   if (base_checksum_hex && fb->base_checksum)
1962     {
1963       const svn_checksum_t *base_md5;
1964       SVN_ERR(svn_checksum_parse_hex(&repos_checksum, svn_checksum_md5,
1965                                      base_checksum_hex, pool));
1966
1967       SVN_ERR(svn_wc__db_pristine_get_md5(&base_md5,
1968                                           eb->db, eb->anchor_abspath,
1969                                           fb->base_checksum,
1970                                           pool, pool));
1971
1972       if (! svn_checksum_match(repos_checksum, base_md5))
1973         {
1974           /* ### I expect that there are some bad drivers out there
1975              ### that used to give bad results. We could look in
1976              ### working to see if the expected checksum matches and
1977              ### then return the pristine of that... But that only moves
1978              ### the problem */
1979
1980           /* If needed: compare checksum obtained via md5 of working.
1981              And if they match set fb->base_checksum and fb->base_props */
1982
1983           return svn_checksum_mismatch_err(
1984                         base_md5,
1985                         repos_checksum,
1986                         pool,
1987                         _("Checksum mismatch for '%s'"),
1988                         svn_dirent_local_style(fb->local_abspath,
1989                                                pool));
1990         }
1991
1992       SVN_ERR(svn_wc__db_pristine_read(&source, NULL,
1993                                        eb->db, fb->local_abspath,
1994                                        fb->base_checksum,
1995                                        pool, pool));
1996     }
1997   else if (fb->base_checksum)
1998     {
1999       SVN_ERR(svn_wc__db_pristine_read(&source, NULL,
2000                                        eb->db, fb->local_abspath,
2001                                        fb->base_checksum,
2002                                        pool, pool));
2003     }
2004   else
2005     source = svn_stream_empty(pool);
2006
2007   /* This is the file that will contain the pristine repository version. */
2008   SVN_ERR(svn_stream_open_unique(&temp_stream, &fb->temp_file_path, NULL,
2009                                  svn_io_file_del_on_pool_cleanup,
2010                                  fb->pool, fb->pool));
2011
2012   svn_txdelta_apply(source, temp_stream,
2013                     fb->result_digest,
2014                     fb->local_abspath /* error_info */,
2015                     fb->pool,
2016                     handler, handler_baton);
2017
2018   return SVN_NO_ERROR;
2019 }
2020
2021 /* An svn_delta_editor_t function.  When the file is closed we have a temporary
2022  * file containing a pristine version of the repository file. This can
2023  * be compared against the working copy.
2024  *
2025  * Ignore TEXT_CHECKSUM.
2026  */
2027 static svn_error_t *
2028 close_file(void *file_baton,
2029            const char *expected_md5_digest,
2030            apr_pool_t *pool)
2031 {
2032   struct file_baton_t *fb = file_baton;
2033   struct dir_baton_t *pb = fb->parent_baton;
2034   struct edit_baton_t *eb = fb->eb;
2035   apr_pool_t *scratch_pool = fb->pool;
2036
2037   /* The repository information; constructed from BASE + Changes */
2038   const char *repos_file;
2039   apr_hash_t *repos_props;
2040
2041   if (fb->skip)
2042     {
2043       svn_pool_destroy(fb->pool); /* destroys scratch_pool and fb */
2044       SVN_ERR(maybe_done(pb));
2045       return SVN_NO_ERROR;
2046     }
2047
2048   if (expected_md5_digest != NULL)
2049     {
2050       svn_checksum_t *expected_checksum;
2051       const svn_checksum_t *result_checksum;
2052
2053       if (fb->temp_file_path)
2054         result_checksum = svn_checksum__from_digest_md5(fb->result_digest,
2055                                                         scratch_pool);
2056       else
2057         result_checksum = fb->base_checksum;
2058
2059       SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
2060                                      expected_md5_digest, scratch_pool));
2061
2062       if (result_checksum->kind != svn_checksum_md5)
2063         SVN_ERR(svn_wc__db_pristine_get_md5(&result_checksum,
2064                                             eb->db, fb->local_abspath,
2065                                             result_checksum,
2066                                             scratch_pool, scratch_pool));
2067
2068       if (!svn_checksum_match(expected_checksum, result_checksum))
2069         return svn_checksum_mismatch_err(
2070                             expected_checksum,
2071                             result_checksum,
2072                             pool,
2073                             _("Checksum mismatch for '%s'"),
2074                             svn_dirent_local_style(fb->local_abspath,
2075                                                    scratch_pool));
2076     }
2077
2078   if (eb->local_before_remote && !fb->repos_only && !fb->ignoring_ancestry)
2079     SVN_ERR(handle_local_only(pb, fb->name, scratch_pool));
2080
2081   {
2082     apr_hash_t *prop_base;
2083
2084     if (fb->added)
2085       prop_base = apr_hash_make(scratch_pool);
2086     else
2087       prop_base = fb->base_props;
2088
2089     /* includes entry props */
2090     repos_props = svn_prop__patch(prop_base, fb->propchanges, scratch_pool);
2091
2092     repos_file = fb->temp_file_path;
2093     if (! repos_file)
2094       {
2095         assert(fb->base_checksum);
2096         SVN_ERR(svn_wc__db_pristine_get_path(&repos_file,
2097                                              eb->db, eb->anchor_abspath,
2098                                              fb->base_checksum,
2099                                              scratch_pool, scratch_pool));
2100       }
2101   }
2102
2103   if (fb->repos_only)
2104     {
2105       SVN_ERR(eb->processor->file_deleted(fb->relpath,
2106                                           fb->left_src,
2107                                           fb->temp_file_path,
2108                                           repos_props,
2109                                           fb->pfb,
2110                                           eb->processor,
2111                                           scratch_pool));
2112     }
2113   else
2114     {
2115       /* Produce a diff of actual or pristine against repos */
2116       apr_hash_t *local_props;
2117       apr_array_header_t *prop_changes;
2118       const char *localfile;
2119
2120       /* pb->local_info contains some information that might allow optimizing
2121          this a bit */
2122
2123       if (eb->diff_pristine)
2124         {
2125           const svn_checksum_t *checksum;
2126           SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL,
2127                                                 NULL, &checksum, NULL, NULL,
2128                                                 &local_props,
2129                                                 eb->db, fb->local_abspath,
2130                                                 scratch_pool, scratch_pool));
2131           assert(checksum);
2132           SVN_ERR(svn_wc__db_pristine_get_path(&localfile,
2133                                                eb->db, eb->anchor_abspath,
2134                                                checksum,
2135                                                scratch_pool, scratch_pool));
2136         }
2137       else
2138         {
2139           SVN_ERR(svn_wc__db_read_props(&local_props,
2140                                         eb->db, fb->local_abspath,
2141                                         scratch_pool, scratch_pool));
2142
2143           /* a detranslated version of the working file */
2144           SVN_ERR(svn_wc__internal_translated_file(
2145                     &localfile, fb->local_abspath, eb->db, fb->local_abspath,
2146                     SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP,
2147                     eb->cancel_func, eb->cancel_baton,
2148                     scratch_pool, scratch_pool));
2149         }
2150
2151       SVN_ERR(svn_prop_diffs(&prop_changes, local_props, repos_props,
2152                              scratch_pool));
2153
2154
2155       /* ### as a good diff processor we should now only report changes, and
2156              report file_closed() in other cases */
2157       SVN_ERR(eb->processor->file_changed(fb->relpath,
2158                                           fb->left_src,
2159                                           fb->right_src,
2160                                           repos_file /* left file */,
2161                                           localfile /* right file */,
2162                                           repos_props /* left_props */,
2163                                           local_props /* right props */,
2164                                           TRUE /* ### file_modified */,
2165                                           prop_changes,
2166                                           fb->pfb,
2167                                           eb->processor,
2168                                           scratch_pool));
2169     }
2170
2171   if (!eb->local_before_remote && !fb->repos_only && !fb->ignoring_ancestry)
2172     SVN_ERR(handle_local_only(pb, fb->name, scratch_pool));
2173
2174   svn_pool_destroy(fb->pool); /* destroys scratch_pool and fb */
2175   SVN_ERR(maybe_done(pb));
2176   return SVN_NO_ERROR;
2177 }
2178
2179
2180 /* An svn_delta_editor_t function. */
2181 static svn_error_t *
2182 change_file_prop(void *file_baton,
2183                  const char *name,
2184                  const svn_string_t *value,
2185                  apr_pool_t *pool)
2186 {
2187   struct file_baton_t *fb = file_baton;
2188   svn_prop_t *propchange;
2189   svn_prop_kind_t propkind;
2190
2191   propkind = svn_property_kind2(name);
2192   if (propkind == svn_prop_wc_kind)
2193     return SVN_NO_ERROR;
2194   else if (propkind == svn_prop_regular_kind)
2195     fb->has_propchange = TRUE;
2196
2197   propchange = apr_array_push(fb->propchanges);
2198   propchange->name = apr_pstrdup(fb->pool, name);
2199   propchange->value = svn_string_dup(value, fb->pool);
2200
2201   return SVN_NO_ERROR;
2202 }
2203
2204
2205 /* An svn_delta_editor_t function. */
2206 static svn_error_t *
2207 change_dir_prop(void *dir_baton,
2208                 const char *name,
2209                 const svn_string_t *value,
2210                 apr_pool_t *pool)
2211 {
2212   struct dir_baton_t *db = dir_baton;
2213   svn_prop_t *propchange;
2214   svn_prop_kind_t propkind;
2215
2216   propkind = svn_property_kind2(name);
2217   if (propkind == svn_prop_wc_kind)
2218     return SVN_NO_ERROR;
2219   else if (propkind == svn_prop_regular_kind)
2220     db->has_propchange = TRUE;
2221
2222   propchange = apr_array_push(db->propchanges);
2223   propchange->name = apr_pstrdup(db->pool, name);
2224   propchange->value = svn_string_dup(value, db->pool);
2225
2226   return SVN_NO_ERROR;
2227 }
2228
2229
2230 /* An svn_delta_editor_t function. */
2231 static svn_error_t *
2232 close_edit(void *edit_baton,
2233            apr_pool_t *pool)
2234 {
2235   struct edit_baton_t *eb = edit_baton;
2236
2237   if (!eb->root_opened)
2238     {
2239       SVN_ERR(walk_local_nodes_diff(eb,
2240                                     eb->anchor_abspath,
2241                                     "",
2242                                     eb->depth,
2243                                     NULL /* compared */,
2244                                     NULL /* No parent_baton */,
2245                                     eb->pool));
2246     }
2247
2248   return SVN_NO_ERROR;
2249 }
2250
2251 /* Public Interface */
2252 \f
2253
2254 /* Create a diff editor and baton. */
2255 svn_error_t *
2256 svn_wc__get_diff_editor(const svn_delta_editor_t **editor,
2257                         void **edit_baton,
2258                         svn_wc_context_t *wc_ctx,
2259                         const char *anchor_abspath,
2260                         const char *target,
2261                         svn_depth_t depth,
2262                         svn_boolean_t ignore_ancestry,
2263                         svn_boolean_t use_text_base,
2264                         svn_boolean_t reverse_order,
2265                         svn_boolean_t server_performs_filtering,
2266                         const apr_array_header_t *changelist_filter,
2267                         const svn_diff_tree_processor_t *diff_processor,
2268                         svn_cancel_func_t cancel_func,
2269                         void *cancel_baton,
2270                         apr_pool_t *result_pool,
2271                         apr_pool_t *scratch_pool)
2272 {
2273   struct edit_baton_t *eb;
2274   void *inner_baton;
2275   svn_delta_editor_t *tree_editor;
2276   const svn_delta_editor_t *inner_editor;
2277   struct svn_wc__shim_fetch_baton_t *sfb;
2278   svn_delta_shim_callbacks_t *shim_callbacks =
2279                                 svn_delta_shim_callbacks_default(result_pool);
2280
2281   SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath));
2282
2283   /* Apply changelist filtering to the output */
2284   if (changelist_filter && changelist_filter->nelts)
2285     {
2286       apr_hash_t *changelist_hash;
2287
2288       SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
2289                                          result_pool));
2290       diff_processor = svn_wc__changelist_filter_tree_processor_create(
2291                          diff_processor, wc_ctx, anchor_abspath,
2292                          changelist_hash, result_pool);
2293     }
2294
2295   SVN_ERR(make_edit_baton(&eb,
2296                           wc_ctx->db,
2297                           anchor_abspath, target,
2298                           diff_processor,
2299                           depth, ignore_ancestry,
2300                           use_text_base, reverse_order,
2301                           cancel_func, cancel_baton,
2302                           result_pool));
2303
2304   tree_editor = svn_delta_default_editor(eb->pool);
2305
2306   tree_editor->set_target_revision = set_target_revision;
2307   tree_editor->open_root = open_root;
2308   tree_editor->delete_entry = delete_entry;
2309   tree_editor->add_directory = add_directory;
2310   tree_editor->open_directory = open_directory;
2311   tree_editor->close_directory = close_directory;
2312   tree_editor->add_file = add_file;
2313   tree_editor->open_file = open_file;
2314   tree_editor->apply_textdelta = apply_textdelta;
2315   tree_editor->change_file_prop = change_file_prop;
2316   tree_editor->change_dir_prop = change_dir_prop;
2317   tree_editor->close_file = close_file;
2318   tree_editor->close_edit = close_edit;
2319
2320   inner_editor = tree_editor;
2321   inner_baton = eb;
2322
2323   if (!server_performs_filtering
2324       && depth == svn_depth_unknown)
2325     SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
2326                                                 &inner_baton,
2327                                                 wc_ctx->db,
2328                                                 anchor_abspath,
2329                                                 target,
2330                                                 inner_editor,
2331                                                 inner_baton,
2332                                                 result_pool));
2333
2334   SVN_ERR(svn_delta_get_cancellation_editor(cancel_func,
2335                                             cancel_baton,
2336                                             inner_editor,
2337                                             inner_baton,
2338                                             editor,
2339                                             edit_baton,
2340                                             result_pool));
2341
2342   sfb = apr_palloc(result_pool, sizeof(*sfb));
2343   sfb->db = wc_ctx->db;
2344   sfb->base_abspath = eb->anchor_abspath;
2345   sfb->fetch_base = TRUE;
2346
2347   shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
2348   shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
2349   shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
2350   shim_callbacks->fetch_baton = sfb;
2351
2352
2353   SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
2354                                    NULL, NULL, shim_callbacks,
2355                                    result_pool, scratch_pool));
2356
2357   return SVN_NO_ERROR;
2358 }
2359
2360 /* Wrapping svn_wc_diff_callbacks4_t as svn_diff_tree_processor_t */
2361
2362 /* baton for the svn_diff_tree_processor_t wrapper */
2363 typedef struct wc_diff_wrap_baton_t
2364 {
2365   const svn_wc_diff_callbacks4_t *callbacks;
2366   void *callback_baton;
2367
2368   svn_boolean_t walk_deleted_dirs;
2369
2370   apr_pool_t *result_pool;
2371   const char *empty_file;
2372
2373 } wc_diff_wrap_baton_t;
2374
2375 static svn_error_t *
2376 wrap_ensure_empty_file(wc_diff_wrap_baton_t *wb,
2377                        apr_pool_t *scratch_pool)
2378 {
2379   if (wb->empty_file)
2380     return SVN_NO_ERROR;
2381
2382   /* Create a unique file in the tempdir */
2383   SVN_ERR(svn_io_open_unique_file3(NULL, &wb->empty_file, NULL,
2384                                    svn_io_file_del_on_pool_cleanup,
2385                                    wb->result_pool, scratch_pool));
2386
2387   return SVN_NO_ERROR;
2388 }
2389
2390 /* svn_diff_tree_processor_t function */
2391 static svn_error_t *
2392 wrap_dir_opened(void **new_dir_baton,
2393                 svn_boolean_t *skip,
2394                 svn_boolean_t *skip_children,
2395                 const char *relpath,
2396                 const svn_diff_source_t *left_source,
2397                 const svn_diff_source_t *right_source,
2398                 const svn_diff_source_t *copyfrom_source,
2399                 void *parent_dir_baton,
2400                 const svn_diff_tree_processor_t *processor,
2401                 apr_pool_t *result_pool,
2402                 apr_pool_t *scratch_pool)
2403 {
2404   wc_diff_wrap_baton_t *wb = processor->baton;
2405   svn_boolean_t tree_conflicted = FALSE;
2406
2407   assert(left_source || right_source);      /* Must exist at one point. */
2408   assert(!left_source || !copyfrom_source); /* Either existed or added. */
2409
2410   /* Maybe store state and tree_conflicted in baton? */
2411   if (left_source != NULL)
2412     {
2413       /* Open for change or delete */
2414       SVN_ERR(wb->callbacks->dir_opened(&tree_conflicted, skip, skip_children,
2415                                         relpath,
2416                                         right_source
2417                                             ? right_source->revision
2418                                             : (left_source
2419                                                     ? left_source->revision
2420                                                     : SVN_INVALID_REVNUM),
2421                                         wb->callback_baton,
2422                                         scratch_pool));
2423
2424       if (! right_source && !wb->walk_deleted_dirs)
2425         *skip_children = TRUE;
2426     }
2427   else /* left_source == NULL -> Add */
2428     {
2429       svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
2430       SVN_ERR(wb->callbacks->dir_added(&state, &tree_conflicted,
2431                                        skip, skip_children,
2432                                        relpath,
2433                                        right_source->revision,
2434                                        copyfrom_source
2435                                             ? copyfrom_source->repos_relpath
2436                                             : NULL,
2437                                        copyfrom_source
2438                                             ? copyfrom_source->revision
2439                                             : SVN_INVALID_REVNUM,
2440                                        wb->callback_baton,
2441                                        scratch_pool));
2442     }
2443
2444   *new_dir_baton = NULL;
2445
2446   return SVN_NO_ERROR;
2447 }
2448
2449 /* svn_diff_tree_processor_t function */
2450 static svn_error_t *
2451 wrap_dir_added(const char *relpath,
2452                const svn_diff_source_t *copyfrom_source,
2453                const svn_diff_source_t *right_source,
2454                /*const*/ apr_hash_t *copyfrom_props,
2455                /*const*/ apr_hash_t *right_props,
2456                void *dir_baton,
2457                const svn_diff_tree_processor_t *processor,
2458                apr_pool_t *scratch_pool)
2459 {
2460   wc_diff_wrap_baton_t *wb = processor->baton;
2461   svn_boolean_t tree_conflicted = FALSE;
2462   svn_wc_notify_state_t state = svn_wc_notify_state_unknown;
2463   svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
2464   apr_hash_t *pristine_props = copyfrom_props;
2465   apr_array_header_t *prop_changes = NULL;
2466
2467   if (right_props && apr_hash_count(right_props))
2468     {
2469       if (!pristine_props)
2470         pristine_props = apr_hash_make(scratch_pool);
2471
2472       SVN_ERR(svn_prop_diffs(&prop_changes, right_props, pristine_props,
2473                              scratch_pool));
2474
2475       SVN_ERR(wb->callbacks->dir_props_changed(&prop_state,
2476                                                &tree_conflicted,
2477                                                relpath,
2478                                                TRUE /* dir_was_added */,
2479                                                prop_changes, pristine_props,
2480                                                wb->callback_baton,
2481                                                scratch_pool));
2482     }
2483
2484   SVN_ERR(wb->callbacks->dir_closed(&state, &prop_state,
2485                                    &tree_conflicted,
2486                                    relpath,
2487                                    TRUE /* dir_was_added */,
2488                                    wb->callback_baton,
2489                                    scratch_pool));
2490   return SVN_NO_ERROR;
2491 }
2492
2493 /* svn_diff_tree_processor_t function */
2494 static svn_error_t *
2495 wrap_dir_deleted(const char *relpath,
2496                  const svn_diff_source_t *left_source,
2497                  /*const*/ apr_hash_t *left_props,
2498                  void *dir_baton,
2499                  const svn_diff_tree_processor_t *processor,
2500                  apr_pool_t *scratch_pool)
2501 {
2502   wc_diff_wrap_baton_t *wb = processor->baton;
2503   svn_boolean_t tree_conflicted = FALSE;
2504   svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
2505
2506   SVN_ERR(wb->callbacks->dir_deleted(&state, &tree_conflicted,
2507                                      relpath,
2508                                      wb->callback_baton,
2509                                      scratch_pool));
2510
2511   return SVN_NO_ERROR;
2512 }
2513
2514 /* svn_diff_tree_processor_t function */
2515 static svn_error_t *
2516 wrap_dir_closed(const char *relpath,
2517                 const svn_diff_source_t *left_source,
2518                 const svn_diff_source_t *right_source,
2519                 void *dir_baton,
2520                 const svn_diff_tree_processor_t *processor,
2521                 apr_pool_t *scratch_pool)
2522 {
2523   wc_diff_wrap_baton_t *wb = processor->baton;
2524
2525   /* No previous implementations provided these arguments, so we
2526      are not providing them either */
2527   SVN_ERR(wb->callbacks->dir_closed(NULL, NULL, NULL,
2528                                     relpath,
2529                                     FALSE /* added */,
2530                                     wb->callback_baton,
2531                                     scratch_pool));
2532
2533 return SVN_NO_ERROR;
2534 }
2535
2536 /* svn_diff_tree_processor_t function */
2537 static svn_error_t *
2538 wrap_dir_changed(const char *relpath,
2539                  const svn_diff_source_t *left_source,
2540                  const svn_diff_source_t *right_source,
2541                  /*const*/ apr_hash_t *left_props,
2542                  /*const*/ apr_hash_t *right_props,
2543                  const apr_array_header_t *prop_changes,
2544                  void *dir_baton,
2545                  const struct svn_diff_tree_processor_t *processor,
2546                  apr_pool_t *scratch_pool)
2547 {
2548   wc_diff_wrap_baton_t *wb = processor->baton;
2549   svn_boolean_t tree_conflicted = FALSE;
2550   svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable;
2551
2552   assert(left_source && right_source);
2553
2554   SVN_ERR(wb->callbacks->dir_props_changed(&prop_state, &tree_conflicted,
2555                                            relpath,
2556                                            FALSE /* dir_was_added */,
2557                                            prop_changes,
2558                                            left_props,
2559                                            wb->callback_baton,
2560                                            scratch_pool));
2561
2562   /* And call dir_closed, etc */
2563   SVN_ERR(wrap_dir_closed(relpath, left_source, right_source,
2564                           dir_baton, processor,
2565                           scratch_pool));
2566   return SVN_NO_ERROR;
2567 }
2568
2569 /* svn_diff_tree_processor_t function */
2570 static svn_error_t *
2571 wrap_file_opened(void **new_file_baton,
2572                  svn_boolean_t *skip,
2573                  const char *relpath,
2574                  const svn_diff_source_t *left_source,
2575                  const svn_diff_source_t *right_source,
2576                  const svn_diff_source_t *copyfrom_source,
2577                  void *dir_baton,
2578                  const svn_diff_tree_processor_t *processor,
2579                  apr_pool_t *result_pool,
2580                  apr_pool_t *scratch_pool)
2581 {
2582   wc_diff_wrap_baton_t *wb = processor->baton;
2583   svn_boolean_t tree_conflicted = FALSE;
2584
2585   if (left_source) /* If ! added */
2586     SVN_ERR(wb->callbacks->file_opened(&tree_conflicted, skip, relpath,
2587                                        right_source
2588                                             ? right_source->revision
2589                                             : (left_source
2590                                                     ? left_source->revision
2591                                                     : SVN_INVALID_REVNUM),
2592                                        wb->callback_baton, scratch_pool));
2593
2594   /* No old implementation used the output arguments for notify */
2595
2596   *new_file_baton = NULL;
2597   return SVN_NO_ERROR;
2598 }
2599
2600 /* svn_diff_tree_processor_t function */
2601 static svn_error_t *
2602 wrap_file_added(const char *relpath,
2603                 const svn_diff_source_t *copyfrom_source,
2604                 const svn_diff_source_t *right_source,
2605                 const char *copyfrom_file,
2606                 const char *right_file,
2607                 /*const*/ apr_hash_t *copyfrom_props,
2608                 /*const*/ apr_hash_t *right_props,
2609                 void *file_baton,
2610                 const svn_diff_tree_processor_t *processor,
2611                 apr_pool_t *scratch_pool)
2612 {
2613   wc_diff_wrap_baton_t *wb = processor->baton;
2614   svn_boolean_t tree_conflicted = FALSE;
2615   svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
2616   svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable;
2617   apr_array_header_t *prop_changes;
2618
2619   if (! copyfrom_props)
2620     copyfrom_props = apr_hash_make(scratch_pool);
2621
2622   SVN_ERR(svn_prop_diffs(&prop_changes, right_props, copyfrom_props,
2623                          scratch_pool));
2624
2625   if (! copyfrom_source)
2626     SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool));
2627
2628   SVN_ERR(wb->callbacks->file_added(&state, &prop_state, &tree_conflicted,
2629                                     relpath,
2630                                     copyfrom_source
2631                                         ? copyfrom_file
2632                                         : wb->empty_file,
2633                                     right_file,
2634                                     0,
2635                                     right_source->revision,
2636                                     copyfrom_props
2637                                      ? svn_prop_get_value(copyfrom_props,
2638                                                           SVN_PROP_MIME_TYPE)
2639                                      : NULL,
2640                                     right_props
2641                                      ? svn_prop_get_value(right_props,
2642                                                           SVN_PROP_MIME_TYPE)
2643                                      : NULL,
2644                                     copyfrom_source
2645                                             ? copyfrom_source->repos_relpath
2646                                             : NULL,
2647                                     copyfrom_source
2648                                             ? copyfrom_source->revision
2649                                             : SVN_INVALID_REVNUM,
2650                                     prop_changes, copyfrom_props,
2651                                     wb->callback_baton,
2652                                     scratch_pool));
2653   return SVN_NO_ERROR;
2654 }
2655
2656 static svn_error_t *
2657 wrap_file_deleted(const char *relpath,
2658                   const svn_diff_source_t *left_source,
2659                   const char *left_file,
2660                   apr_hash_t *left_props,
2661                   void *file_baton,
2662                   const svn_diff_tree_processor_t *processor,
2663                   apr_pool_t *scratch_pool)
2664 {
2665   wc_diff_wrap_baton_t *wb = processor->baton;
2666   svn_boolean_t tree_conflicted = FALSE;
2667   svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
2668
2669   SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool));
2670
2671   SVN_ERR(wb->callbacks->file_deleted(&state, &tree_conflicted,
2672                                       relpath,
2673                                       left_file, wb->empty_file,
2674                                       left_props
2675                                        ? svn_prop_get_value(left_props,
2676                                                             SVN_PROP_MIME_TYPE)
2677                                        : NULL,
2678                                       NULL,
2679                                       left_props,
2680                                       wb->callback_baton,
2681                                       scratch_pool));
2682   return SVN_NO_ERROR;
2683 }
2684
2685 /* svn_diff_tree_processor_t function */
2686 static svn_error_t *
2687 wrap_file_changed(const char *relpath,
2688                   const svn_diff_source_t *left_source,
2689                   const svn_diff_source_t *right_source,
2690                   const char *left_file,
2691                   const char *right_file,
2692                   /*const*/ apr_hash_t *left_props,
2693                   /*const*/ apr_hash_t *right_props,
2694                   svn_boolean_t file_modified,
2695                   const apr_array_header_t *prop_changes,
2696                   void *file_baton,
2697                   const svn_diff_tree_processor_t *processor,
2698                   apr_pool_t *scratch_pool)
2699 {
2700   wc_diff_wrap_baton_t *wb = processor->baton;
2701   svn_boolean_t tree_conflicted = FALSE;
2702   svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
2703   svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable;
2704
2705   SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool));
2706
2707   assert(left_source && right_source);
2708
2709   SVN_ERR(wb->callbacks->file_changed(&state, &prop_state, &tree_conflicted,
2710                                       relpath,
2711                                       file_modified ? left_file : NULL,
2712                                       file_modified ? right_file : NULL,
2713                                       left_source->revision,
2714                                       right_source->revision,
2715                                       left_props
2716                                        ? svn_prop_get_value(left_props,
2717                                                             SVN_PROP_MIME_TYPE)
2718                                        : NULL,
2719                                       right_props
2720                                        ? svn_prop_get_value(right_props,
2721                                                             SVN_PROP_MIME_TYPE)
2722                                        : NULL,
2723                                        prop_changes,
2724                                       left_props,
2725                                       wb->callback_baton,
2726                                       scratch_pool));
2727   return SVN_NO_ERROR;
2728 }
2729
2730 svn_error_t *
2731 svn_wc__wrap_diff_callbacks(const svn_diff_tree_processor_t **diff_processor,
2732                             const svn_wc_diff_callbacks4_t *callbacks,
2733                             void *callback_baton,
2734                             svn_boolean_t walk_deleted_dirs,
2735                             apr_pool_t *result_pool,
2736                             apr_pool_t *scratch_pool)
2737 {
2738   wc_diff_wrap_baton_t *wrap_baton;
2739   svn_diff_tree_processor_t *processor;
2740
2741   wrap_baton = apr_pcalloc(result_pool, sizeof(*wrap_baton));
2742
2743   wrap_baton->result_pool = result_pool;
2744   wrap_baton->callbacks = callbacks;
2745   wrap_baton->callback_baton = callback_baton;
2746   wrap_baton->empty_file = NULL;
2747   wrap_baton->walk_deleted_dirs = walk_deleted_dirs;
2748
2749   processor = svn_diff__tree_processor_create(wrap_baton, result_pool);
2750
2751   processor->dir_opened   = wrap_dir_opened;
2752   processor->dir_added    = wrap_dir_added;
2753   processor->dir_deleted  = wrap_dir_deleted;
2754   processor->dir_changed  = wrap_dir_changed;
2755   processor->dir_closed   = wrap_dir_closed;
2756
2757   processor->file_opened   = wrap_file_opened;
2758   processor->file_added    = wrap_file_added;
2759   processor->file_deleted  = wrap_file_deleted;
2760   processor->file_changed  = wrap_file_changed;
2761   /*processor->file_closed   = wrap_file_closed*/; /* Not needed */
2762
2763   *diff_processor = processor;
2764   return SVN_NO_ERROR;
2765 }
2766
2767 /* =====================================================================
2768  * A tree processor filter that filters by changelist membership
2769  * =====================================================================
2770  *
2771  * The current implementation queries the WC for the changelist of each
2772  * file as it comes through, and sets the 'skip' flag for a non-matching
2773  * file.
2774  *
2775  * (It doesn't set the 'skip' flag for a directory, as we need to receive
2776  * the changed/added/deleted/closed call to know when it is closed, in
2777  * order to preserve the strict open-close semantics for the wrapped tree
2778  * processor.)
2779  *
2780  * It passes on the opening and closing of every directory, even if there
2781  * are no file changes to be passed on inside that directory.
2782  */
2783
2784 typedef struct filter_tree_baton_t
2785 {
2786   const svn_diff_tree_processor_t *processor;
2787   svn_wc_context_t *wc_ctx;
2788   /* WC path of the root of the diff (where relpath = "") */
2789   const char *root_local_abspath;
2790   /* Hash whose keys are const char * changelist names. */
2791   apr_hash_t *changelist_hash;
2792 } filter_tree_baton_t;
2793
2794 static svn_error_t *
2795 filter_dir_opened(void **new_dir_baton,
2796                   svn_boolean_t *skip,
2797                   svn_boolean_t *skip_children,
2798                   const char *relpath,
2799                   const svn_diff_source_t *left_source,
2800                   const svn_diff_source_t *right_source,
2801                   const svn_diff_source_t *copyfrom_source,
2802                   void *parent_dir_baton,
2803                   const svn_diff_tree_processor_t *processor,
2804                   apr_pool_t *result_pool,
2805                   apr_pool_t *scratch_pool)
2806 {
2807   struct filter_tree_baton_t *fb = processor->baton;
2808
2809   SVN_ERR(fb->processor->dir_opened(new_dir_baton, skip, skip_children,
2810                                     relpath,
2811                                     left_source, right_source,
2812                                     copyfrom_source,
2813                                     parent_dir_baton,
2814                                     fb->processor,
2815                                     result_pool, scratch_pool));
2816   return SVN_NO_ERROR;
2817 }
2818
2819 static svn_error_t *
2820 filter_dir_added(const char *relpath,
2821                  const svn_diff_source_t *copyfrom_source,
2822                  const svn_diff_source_t *right_source,
2823                  /*const*/ apr_hash_t *copyfrom_props,
2824                  /*const*/ apr_hash_t *right_props,
2825                  void *dir_baton,
2826                  const svn_diff_tree_processor_t *processor,
2827                  apr_pool_t *scratch_pool)
2828 {
2829   struct filter_tree_baton_t *fb = processor->baton;
2830
2831   SVN_ERR(fb->processor->dir_closed(relpath,
2832                                     NULL,
2833                                     right_source,
2834                                     dir_baton,
2835                                     fb->processor,
2836                                     scratch_pool));
2837
2838   return SVN_NO_ERROR;
2839 }
2840
2841 static svn_error_t *
2842 filter_dir_deleted(const char *relpath,
2843                    const svn_diff_source_t *left_source,
2844                    /*const*/ apr_hash_t *left_props,
2845                    void *dir_baton,
2846                    const svn_diff_tree_processor_t *processor,
2847                    apr_pool_t *scratch_pool)
2848 {
2849   struct filter_tree_baton_t *fb = processor->baton;
2850
2851   SVN_ERR(fb->processor->dir_closed(relpath,
2852                                     left_source,
2853                                     NULL,
2854                                     dir_baton,
2855                                     fb->processor,
2856                                     scratch_pool));
2857
2858   return SVN_NO_ERROR;
2859 }
2860
2861 static svn_error_t *
2862 filter_dir_changed(const char *relpath,
2863                    const svn_diff_source_t *left_source,
2864                    const svn_diff_source_t *right_source,
2865                    /*const*/ apr_hash_t *left_props,
2866                    /*const*/ apr_hash_t *right_props,
2867                    const apr_array_header_t *prop_changes,
2868                    void *dir_baton,
2869                    const struct svn_diff_tree_processor_t *processor,
2870                    apr_pool_t *scratch_pool)
2871 {
2872   struct filter_tree_baton_t *fb = processor->baton;
2873
2874   SVN_ERR(fb->processor->dir_closed(relpath,
2875                                     left_source,
2876                                     right_source,
2877                                     dir_baton,
2878                                     fb->processor,
2879                                     scratch_pool));
2880   return SVN_NO_ERROR;
2881 }
2882
2883 static svn_error_t *
2884 filter_dir_closed(const char *relpath,
2885                   const svn_diff_source_t *left_source,
2886                   const svn_diff_source_t *right_source,
2887                   void *dir_baton,
2888                   const svn_diff_tree_processor_t *processor,
2889                   apr_pool_t *scratch_pool)
2890 {
2891   struct filter_tree_baton_t *fb = processor->baton;
2892
2893   SVN_ERR(fb->processor->dir_closed(relpath,
2894                                     left_source,
2895                                     right_source,
2896                                     dir_baton,
2897                                     fb->processor,
2898                                     scratch_pool));
2899   return SVN_NO_ERROR;
2900 }
2901
2902 static svn_error_t *
2903 filter_file_opened(void **new_file_baton,
2904                    svn_boolean_t *skip,
2905                    const char *relpath,
2906                    const svn_diff_source_t *left_source,
2907                    const svn_diff_source_t *right_source,
2908                    const svn_diff_source_t *copyfrom_source,
2909                    void *dir_baton,
2910                    const svn_diff_tree_processor_t *processor,
2911                    apr_pool_t *result_pool,
2912                    apr_pool_t *scratch_pool)
2913 {
2914   struct filter_tree_baton_t *fb = processor->baton;
2915   const char *local_abspath
2916     = svn_dirent_join(fb->root_local_abspath, relpath, scratch_pool);
2917
2918   /* Skip if not a member of a given changelist */
2919   if (! svn_wc__changelist_match(fb->wc_ctx, local_abspath,
2920                                  fb->changelist_hash, scratch_pool))
2921     {
2922       *skip = TRUE;
2923       return SVN_NO_ERROR;
2924     }
2925
2926   SVN_ERR(fb->processor->file_opened(new_file_baton,
2927                                      skip,
2928                                      relpath,
2929                                      left_source,
2930                                      right_source,
2931                                      copyfrom_source,
2932                                      dir_baton,
2933                                      fb->processor,
2934                                      result_pool,
2935                                      scratch_pool));
2936   return SVN_NO_ERROR;
2937 }
2938
2939 static svn_error_t *
2940 filter_file_added(const char *relpath,
2941                   const svn_diff_source_t *copyfrom_source,
2942                   const svn_diff_source_t *right_source,
2943                   const char *copyfrom_file,
2944                   const char *right_file,
2945                   /*const*/ apr_hash_t *copyfrom_props,
2946                   /*const*/ apr_hash_t *right_props,
2947                   void *file_baton,
2948                   const svn_diff_tree_processor_t *processor,
2949                   apr_pool_t *scratch_pool)
2950 {
2951   struct filter_tree_baton_t *fb = processor->baton;
2952
2953   SVN_ERR(fb->processor->file_added(relpath,
2954                                     copyfrom_source,
2955                                     right_source,
2956                                     copyfrom_file,
2957                                     right_file,
2958                                     copyfrom_props,
2959                                     right_props,
2960                                     file_baton,
2961                                     fb->processor,
2962                                     scratch_pool));
2963   return SVN_NO_ERROR;
2964 }
2965
2966 static svn_error_t *
2967 filter_file_deleted(const char *relpath,
2968                     const svn_diff_source_t *left_source,
2969                     const char *left_file,
2970                     /*const*/ apr_hash_t *left_props,
2971                     void *file_baton,
2972                     const svn_diff_tree_processor_t *processor,
2973                     apr_pool_t *scratch_pool)
2974 {
2975   struct filter_tree_baton_t *fb = processor->baton;
2976
2977   SVN_ERR(fb->processor->file_deleted(relpath,
2978                                       left_source,
2979                                       left_file,
2980                                       left_props,
2981                                       file_baton,
2982                                       fb->processor,
2983                                       scratch_pool));
2984
2985   return SVN_NO_ERROR;
2986 }
2987
2988 static svn_error_t *
2989 filter_file_changed(const char *relpath,
2990                     const svn_diff_source_t *left_source,
2991                     const svn_diff_source_t *right_source,
2992                     const char *left_file,
2993                     const char *right_file,
2994                     /*const*/ apr_hash_t *left_props,
2995                     /*const*/ apr_hash_t *right_props,
2996                     svn_boolean_t file_modified,
2997                     const apr_array_header_t *prop_changes,
2998                     void *file_baton,
2999                     const svn_diff_tree_processor_t *processor,
3000                     apr_pool_t *scratch_pool)
3001 {
3002   struct filter_tree_baton_t *fb = processor->baton;
3003
3004   SVN_ERR(fb->processor->file_changed(relpath,
3005                                       left_source,
3006                                       right_source,
3007                                       left_file,
3008                                       right_file,
3009                                       left_props,
3010                                       right_props,
3011                                       file_modified,
3012                                       prop_changes,
3013                                       file_baton,
3014                                       fb->processor,
3015                                       scratch_pool));
3016   return SVN_NO_ERROR;
3017 }
3018
3019 static svn_error_t *
3020 filter_file_closed(const char *relpath,
3021                    const svn_diff_source_t *left_source,
3022                    const svn_diff_source_t *right_source,
3023                    void *file_baton,
3024                    const svn_diff_tree_processor_t *processor,
3025                    apr_pool_t *scratch_pool)
3026 {
3027   struct filter_tree_baton_t *fb = processor->baton;
3028
3029   SVN_ERR(fb->processor->file_closed(relpath,
3030                                      left_source,
3031                                      right_source,
3032                                      file_baton,
3033                                      fb->processor,
3034                                      scratch_pool));
3035
3036   return SVN_NO_ERROR;
3037 }
3038
3039 static svn_error_t *
3040 filter_node_absent(const char *relpath,
3041                    void *dir_baton,
3042                    const svn_diff_tree_processor_t *processor,
3043                    apr_pool_t *scratch_pool)
3044 {
3045   struct filter_tree_baton_t *fb = processor->baton;
3046
3047   SVN_ERR(fb->processor->node_absent(relpath,
3048                                      dir_baton,
3049                                      fb->processor,
3050                                      scratch_pool));
3051   return SVN_NO_ERROR;
3052 }
3053
3054 const svn_diff_tree_processor_t *
3055 svn_wc__changelist_filter_tree_processor_create(
3056                                 const svn_diff_tree_processor_t *processor,
3057                                 svn_wc_context_t *wc_ctx,
3058                                 const char *root_local_abspath,
3059                                 apr_hash_t *changelist_hash,
3060                                 apr_pool_t *result_pool)
3061 {
3062   struct filter_tree_baton_t *fb;
3063   svn_diff_tree_processor_t *filter;
3064
3065   if (! changelist_hash)
3066     return processor;
3067
3068   fb = apr_pcalloc(result_pool, sizeof(*fb));
3069   fb->processor = processor;
3070   fb->wc_ctx = wc_ctx;
3071   fb->root_local_abspath = root_local_abspath;
3072   fb->changelist_hash = changelist_hash;
3073
3074   filter = svn_diff__tree_processor_create(fb, result_pool);
3075   filter->dir_opened   = filter_dir_opened;
3076   filter->dir_added    = filter_dir_added;
3077   filter->dir_deleted  = filter_dir_deleted;
3078   filter->dir_changed  = filter_dir_changed;
3079   filter->dir_closed   = filter_dir_closed;
3080
3081   filter->file_opened   = filter_file_opened;
3082   filter->file_added    = filter_file_added;
3083   filter->file_deleted  = filter_file_deleted;
3084   filter->file_changed  = filter_file_changed;
3085   filter->file_closed   = filter_file_closed;
3086
3087   filter->node_absent   = filter_node_absent;
3088
3089   return filter;
3090 }
3091