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