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