1 /*
2  * repos_diff.c -- The diff editor for comparing two repository versions
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 /* This code uses an editor driven by a tree delta between two
25  * repository revisions (REV1 and REV2). For each file encountered in
26  * the delta the editor constructs two temporary files, one for each
27  * revision. This necessitates a separate request for the REV1 version
28  * of the file when the delta shows the file being modified or
29  * deleted. Files that are added by the delta do not require a
30  * separate request, the REV1 version is empty and the delta is
31  * sufficient to construct the REV2 version. When both versions of
32  * each file have been created the diff callback is invoked to display
33  * the difference between the two files.  */
34 
35 #include <apr_uri.h>
36 #include <apr_md5.h>
37 #include <assert.h>
38 
39 #include "svn_checksum.h"
40 #include "svn_hash.h"
41 #include "svn_wc.h"
42 #include "svn_pools.h"
43 #include "svn_dirent_uri.h"
44 #include "svn_path.h"
45 #include "svn_io.h"
46 #include "svn_props.h"
47 #include "svn_private_config.h"
48 
49 #include "client.h"
50 
51 #include "private/svn_subr_private.h"
52 #include "private/svn_wc_private.h"
53 #include "private/svn_editor.h"
54 #include "private/svn_sorts_private.h"
55 
56 /* Overall crawler editor baton.  */
57 struct edit_baton {
58   /* The passed depth */
59   svn_depth_t depth;
60 
61   /* The result processor */
62   const svn_diff_tree_processor_t *processor;
63 
64   /* RA_SESSION is the open session for making requests to the RA layer */
65   svn_ra_session_t *ra_session;
66 
67   /* The rev1 from the '-r Rev1:Rev2' command line option */
68   svn_revnum_t revision;
69 
70   /* The rev2 from the '-r Rev1:Rev2' option, specifically set by
71      set_target_revision(). */
72   svn_revnum_t target_revision;
73 
74   /* The path to a temporary empty file used for add/delete
75      differences.  The path is cached here so that it can be reused,
76      since all empty files are the same. */
77   const char *empty_file;
78 
79   /* Empty hash used for adds. */
80   apr_hash_t *empty_hash;
81 
82   /* Whether to report text deltas */
83   svn_boolean_t text_deltas;
84 
85   /* A callback used to see if the client wishes to cancel the running
86      operation. */
87   svn_cancel_func_t cancel_func;
88 
89   /* A baton to pass to the cancellation callback. */
90   void *cancel_baton;
91 
92   apr_pool_t *pool;
93 };
94 
95 typedef struct deleted_path_notify_t
96 {
97   svn_node_kind_t kind;
98   svn_wc_notify_action_t action;
99   svn_wc_notify_state_t state;
100   svn_boolean_t tree_conflicted;
101 } deleted_path_notify_t;
102 
103 /* Directory level baton.
104  */
105 struct dir_baton {
106   /* Gets set if the directory is added rather than replaced/unchanged. */
107   svn_boolean_t added;
108 
109   /* Gets set if this operation caused a tree-conflict on this directory
110    * (does not show tree-conflicts persisting from before this operation). */
111   svn_boolean_t tree_conflicted;
112 
113   /* If TRUE, this node is skipped entirely.
114    * This is used to skip all children of a tree-conflicted
115    * directory without setting TREE_CONFLICTED to TRUE everywhere. */
116   svn_boolean_t skip;
117 
118   /* If TRUE, all children of this directory are skipped. */
119   svn_boolean_t skip_children;
120 
121   /* The path of the directory within the repository */
122   const char *path;
123 
124   /* The baton for the parent directory, or null if this is the root of the
125      hierarchy to be compared. */
126   struct dir_baton *parent_baton;
127 
128   /* The overall crawler editor baton. */
129   struct edit_baton *edit_baton;
130 
131   /* A cache of any property changes (svn_prop_t) received for this dir. */
132   apr_array_header_t *propchanges;
133 
134   /* Boolean indicating whether a node property was changed */
135   svn_boolean_t has_propchange;
136 
137   /* Baton for svn_diff_tree_processor_t */
138   void *pdb;
139   svn_diff_source_t *left_source;
140   svn_diff_source_t *right_source;
141 
142   /* The pool passed in by add_dir, open_dir, or open_root.
143      Also, the pool this dir baton is allocated in. */
144   apr_pool_t *pool;
145 
146   /* Base revision of directory. */
147   svn_revnum_t base_revision;
148 
149   /* Number of users of baton. Its pool will be destroyed 0 */
150   int users;
151 };
152 
153 /* File level baton.
154  */
155 struct file_baton {
156   /* Reference to parent baton */
157   struct dir_baton *parent_baton;
158 
159   /* Gets set if the file is added rather than replaced. */
160   svn_boolean_t added;
161 
162   /* Gets set if this operation caused a tree-conflict on this file
163    * (does not show tree-conflicts persisting from before this operation). */
164   svn_boolean_t tree_conflicted;
165 
166   /* If TRUE, this node is skipped entirely.
167    * This is currently used to skip all children of a tree-conflicted
168    * directory. */
169   svn_boolean_t skip;
170 
171   /* The path of the file within the repository */
172   const char *path;
173 
174   /* The path and APR file handle to the temporary file that contains the
175      first repository version.  Also, the pristine-property list of
176      this file. */
177   const char *path_start_revision;
178   apr_hash_t *pristine_props;
179   svn_revnum_t base_revision;
180 
181   /* The path and APR file handle to the temporary file that contains the
182      second repository version.  These fields are set when processing
183      textdelta and file deletion, and will be NULL if there's no
184      textual difference between the two revisions. */
185   const char *path_end_revision;
186 
187   /* APPLY_HANDLER/APPLY_BATON represent the delta application baton. */
188   svn_txdelta_window_handler_t apply_handler;
189   void *apply_baton;
190 
191   /* The overall crawler editor baton. */
192   struct edit_baton *edit_baton;
193 
194   /* Holds the checksum of the start revision file */
195   svn_checksum_t *start_md5_checksum;
196 
197   /* Holds the resulting md5 digest of a textdelta transform */
198   unsigned char result_digest[APR_MD5_DIGESTSIZE];
199   svn_checksum_t *result_md5_checksum;
200 
201   /* A cache of any property changes (svn_prop_t) received for this file. */
202   apr_array_header_t *propchanges;
203 
204   /* Boolean indicating whether a node property was changed */
205   svn_boolean_t has_propchange;
206 
207   /* Baton for svn_diff_tree_processor_t */
208   void *pfb;
209   svn_diff_source_t *left_source;
210   svn_diff_source_t *right_source;
211 
212   /* The pool passed in by add_file or open_file.
213      Also, the pool this file_baton is allocated in. */
214   apr_pool_t *pool;
215 };
216 
217 /* Create a new directory baton for PATH in POOL.  ADDED is set if
218  * this directory is being added rather than replaced. PARENT_BATON is
219  * the baton of the parent directory (or NULL if this is the root of
220  * the comparison hierarchy). The directory and its parent may or may
221  * not exist in the working copy.  EDIT_BATON is the overall crawler
222  * editor baton.
223  */
224 static struct dir_baton *
make_dir_baton(const char * path,struct dir_baton * parent_baton,struct edit_baton * edit_baton,svn_boolean_t added,svn_revnum_t base_revision,apr_pool_t * result_pool)225 make_dir_baton(const char *path,
226                struct dir_baton *parent_baton,
227                struct edit_baton *edit_baton,
228                svn_boolean_t added,
229                svn_revnum_t base_revision,
230                apr_pool_t *result_pool)
231 {
232   apr_pool_t *dir_pool = svn_pool_create(result_pool);
233   struct dir_baton *dir_baton = apr_pcalloc(dir_pool, sizeof(*dir_baton));
234 
235   dir_baton->parent_baton = parent_baton;
236   dir_baton->edit_baton = edit_baton;
237   dir_baton->added = added;
238   dir_baton->tree_conflicted = FALSE;
239   dir_baton->skip = FALSE;
240   dir_baton->skip_children = FALSE;
241   dir_baton->pool = dir_pool;
242   dir_baton->path = apr_pstrdup(dir_pool, path);
243   dir_baton->propchanges  = apr_array_make(dir_pool, 8, sizeof(svn_prop_t));
244   dir_baton->base_revision = base_revision;
245   dir_baton->users++;
246 
247   if (parent_baton)
248     parent_baton->users++;
249 
250   return dir_baton;
251 }
252 
253 /* New function. Called by everyone who has a reference when done */
254 static svn_error_t *
release_dir(struct dir_baton * db)255 release_dir(struct dir_baton *db)
256 {
257   assert(db->users > 0);
258 
259   db->users--;
260   if (db->users)
261      return SVN_NO_ERROR;
262 
263   {
264     struct dir_baton *pb = db->parent_baton;
265 
266     svn_pool_destroy(db->pool);
267 
268     if (pb != NULL)
269       SVN_ERR(release_dir(pb));
270   }
271 
272   return SVN_NO_ERROR;
273 }
274 
275 /* Create a new file baton for PATH in POOL, which is a child of
276  * directory PARENT_PATH. ADDED is set if this file is being added
277  * rather than replaced.  EDIT_BATON is a pointer to the global edit
278  * baton.
279  */
280 static struct file_baton *
make_file_baton(const char * path,struct dir_baton * parent_baton,svn_boolean_t added,apr_pool_t * result_pool)281 make_file_baton(const char *path,
282                 struct dir_baton *parent_baton,
283                 svn_boolean_t added,
284                 apr_pool_t *result_pool)
285 {
286   apr_pool_t *file_pool = svn_pool_create(result_pool);
287   struct file_baton *file_baton = apr_pcalloc(file_pool, sizeof(*file_baton));
288 
289   file_baton->parent_baton = parent_baton;
290   file_baton->edit_baton = parent_baton->edit_baton;
291   file_baton->added = added;
292   file_baton->tree_conflicted = FALSE;
293   file_baton->skip = FALSE;
294   file_baton->pool = file_pool;
295   file_baton->path = apr_pstrdup(file_pool, path);
296   file_baton->propchanges  = apr_array_make(file_pool, 8, sizeof(svn_prop_t));
297   file_baton->base_revision = parent_baton->edit_baton->revision;
298 
299   parent_baton->users++;
300 
301   return file_baton;
302 }
303 
304 /* Get revision FB->base_revision of the file described by FB from the
305  * repository, through FB->edit_baton->ra_session.
306  *
307  * Unless PROPS_ONLY is true:
308  *   Set FB->path_start_revision to the path of a new temporary file containing
309  *   the file's text.
310  *   Set FB->start_md5_checksum to that file's MD-5 checksum.
311  *   Install a pool cleanup handler on FB->pool to delete the file.
312  *
313  * Always:
314  *   Set FB->pristine_props to a new hash containing the file's properties.
315  *
316  * Allocate all results in FB->pool.
317  */
318 static svn_error_t *
get_file_from_ra(struct file_baton * fb,svn_boolean_t props_only,apr_pool_t * scratch_pool)319 get_file_from_ra(struct file_baton *fb,
320                  svn_boolean_t props_only,
321                  apr_pool_t *scratch_pool)
322 {
323   if (! props_only)
324     {
325       svn_stream_t *fstream;
326 
327       SVN_ERR(svn_stream_open_unique(&fstream, &(fb->path_start_revision),
328                                      NULL, svn_io_file_del_on_pool_cleanup,
329                                      fb->pool, scratch_pool));
330 
331       fstream = svn_stream_checksummed2(fstream, NULL, &fb->start_md5_checksum,
332                                         svn_checksum_md5, TRUE, fb->pool);
333 
334       /* Retrieve the file and its properties */
335       SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session,
336                               fb->path,
337                               fb->base_revision,
338                               fstream, NULL,
339                               &(fb->pristine_props),
340                               fb->pool));
341       SVN_ERR(svn_stream_close(fstream));
342     }
343   else
344     {
345       SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session,
346                               fb->path,
347                               fb->base_revision,
348                               NULL, NULL,
349                               &(fb->pristine_props),
350                               fb->pool));
351     }
352 
353   return SVN_NO_ERROR;
354 }
355 
356 /* Remove every no-op property change from CHANGES: that is, remove every
357    entry in which the target value is the same as the value of the
358    corresponding property in PRISTINE_PROPS.
359 
360      Issue #3657 'dav update report handler in skelta mode can cause
361      spurious conflicts'.  When communicating with the repository via ra_serf,
362      the change_dir_prop and change_file_prop svn_delta_editor_t
363      callbacks are called (obviously) when a directory or file property has
364      changed between the start and end of the edit.  Less obvious however,
365      is that these callbacks may be made describing *all* of the properties
366      on FILE_BATON->PATH when using the DAV providers, not just the change(s).
367      (Specifically ra_serf does it for diff/merge/update/switch).
368 
369      This means that the change_[file|dir]_prop svn_delta_editor_t callbacks
370      may be made where there are no property changes (i.e. a noop change of
371      NAME from VALUE to VALUE).  Normally this is harmless, but during a
372      merge it can result in spurious conflicts if the WC's pristine property
373      NAME has a value other than VALUE.  In an ideal world the mod_dav_svn
374      update report handler, when in 'skelta' mode and describing changes to
375      a path on which a property has changed, wouldn't ask the client to later
376      fetch all properties and figure out what has changed itself.  The server
377      already knows which properties have changed!
378 
379      Regardless, such a change is not yet implemented, and even when it is,
380      the client should DTRT with regard to older servers which behave this
381      way.  Hence this little hack:  We populate FILE_BATON->PROPCHANGES only
382      with *actual* property changes.
383 
384      See https://issues.apache.org/jira/browse/SVN-3657#desc9 and
385      http://svn.haxx.se/dev/archive-2010-08/0351.shtml for more details.
386  */
387 static svn_error_t *
remove_non_prop_changes(apr_hash_t * pristine_props,apr_array_header_t * changes)388 remove_non_prop_changes(apr_hash_t *pristine_props,
389                         apr_array_header_t *changes)
390 {
391   int i;
392 
393   /* For added nodes, there is nothing to filter. */
394   if (apr_hash_count(pristine_props) == 0)
395     return SVN_NO_ERROR;
396 
397   for (i = 0; i < changes->nelts; i++)
398     {
399       svn_prop_t *change = &APR_ARRAY_IDX(changes, i, svn_prop_t);
400 
401       if (change->value)
402         {
403           const svn_string_t *old_val = svn_hash_gets(pristine_props,
404                                                       change->name);
405 
406           if (old_val && svn_string_compare(old_val, change->value))
407             {
408               /* Remove the matching change and re-check the current index */
409               SVN_ERR(svn_sort__array_delete2(changes, i, 1));
410               i--;
411             }
412         }
413     }
414   return SVN_NO_ERROR;
415 }
416 
417 /* Get the empty file associated with the edit baton. This is cached so
418  * that it can be reused, all empty files are the same.
419  */
420 static svn_error_t *
get_empty_file(struct edit_baton * eb,const char ** empty_file_path)421 get_empty_file(struct edit_baton *eb,
422                const char **empty_file_path)
423 {
424   /* Create the file if it does not exist */
425   /* Note that we tried to use /dev/null in r857294, but
426      that won't work on Windows: it's impossible to stat NUL */
427   if (!eb->empty_file)
428     SVN_ERR(svn_io_open_unique_file3(NULL, &(eb->empty_file), NULL,
429                                      svn_io_file_del_on_pool_cleanup,
430                                      eb->pool, eb->pool));
431 
432   *empty_file_path = eb->empty_file;
433 
434   return SVN_NO_ERROR;
435 }
436 
437 /* An svn_delta_editor_t function.  */
438 static svn_error_t *
set_target_revision(void * edit_baton,svn_revnum_t target_revision,apr_pool_t * pool)439 set_target_revision(void *edit_baton,
440                     svn_revnum_t target_revision,
441                     apr_pool_t *pool)
442 {
443   struct edit_baton *eb = edit_baton;
444 
445   eb->target_revision = target_revision;
446   return SVN_NO_ERROR;
447 }
448 
449 /* An svn_delta_editor_t function. The root of the comparison hierarchy */
450 static svn_error_t *
open_root(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * pool,void ** root_baton)451 open_root(void *edit_baton,
452           svn_revnum_t base_revision,
453           apr_pool_t *pool,
454           void **root_baton)
455 {
456   struct edit_baton *eb = edit_baton;
457   struct dir_baton *db = make_dir_baton("", NULL, eb, FALSE, base_revision,
458                                         eb->pool);
459 
460   db->left_source = svn_diff__source_create(eb->revision, db->pool);
461   db->right_source = svn_diff__source_create(eb->target_revision, db->pool);
462 
463   SVN_ERR(eb->processor->dir_opened(&db->pdb,
464                                     &db->skip,
465                                     &db->skip_children,
466                                     "",
467                                     db->left_source,
468                                     db->right_source,
469                                     NULL,
470                                     NULL,
471                                     eb->processor,
472                                     db->pool,
473                                     db->pool /* scratch_pool */));
474 
475   *root_baton = db;
476   return SVN_NO_ERROR;
477 }
478 
479 /* Compare a file being deleted against an empty file.
480  */
481 static svn_error_t *
diff_deleted_file(const char * path,struct dir_baton * db,apr_pool_t * scratch_pool)482 diff_deleted_file(const char *path,
483                   struct dir_baton *db,
484                   apr_pool_t *scratch_pool)
485 {
486   struct edit_baton *eb = db->edit_baton;
487   struct file_baton *fb = make_file_baton(path, db, FALSE, scratch_pool);
488   svn_boolean_t skip = FALSE;
489   svn_diff_source_t *left_source = svn_diff__source_create(eb->revision,
490                                                            scratch_pool);
491 
492   if (eb->cancel_func)
493     SVN_ERR(eb->cancel_func(eb->cancel_baton));
494 
495   SVN_ERR(eb->processor->file_opened(&fb->pfb, &skip, path,
496                                      left_source,
497                                      NULL /* right_source */,
498                                      NULL /* copyfrom_source */,
499                                      db->pdb,
500                                      eb->processor,
501                                      scratch_pool, scratch_pool));
502 
503   if (eb->cancel_func)
504     SVN_ERR(eb->cancel_func(eb->cancel_baton));
505 
506   if (skip)
507     return SVN_NO_ERROR;
508 
509   SVN_ERR(get_file_from_ra(fb, ! eb->text_deltas, scratch_pool));
510 
511   SVN_ERR(eb->processor->file_deleted(fb->path,
512                                       left_source,
513                                       fb->path_start_revision,
514                                       fb->pristine_props,
515                                       fb->pfb,
516                                       eb->processor,
517                                       scratch_pool));
518 
519   return SVN_NO_ERROR;
520 }
521 
522 /* Recursively walk tree rooted at DIR (at EB->revision) in the repository,
523  * reporting all children as deleted.  Part of a workaround for issue 2333.
524  *
525  * DIR is a repository path relative to the URL in EB->ra_session.  EB is
526  * the overall crawler editor baton.  EB->revision must be a valid revision
527  * number, not SVN_INVALID_REVNUM.  Use EB->cancel_func (if not null) with
528  * EB->cancel_baton for cancellation.
529  */
530 /* ### TODO: Handle depth. */
531 static svn_error_t *
diff_deleted_dir(const char * path,struct dir_baton * pb,apr_pool_t * scratch_pool)532 diff_deleted_dir(const char *path,
533                  struct dir_baton *pb,
534                  apr_pool_t *scratch_pool)
535 {
536   struct edit_baton *eb = pb->edit_baton;
537   struct dir_baton *db;
538   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
539   svn_boolean_t skip = FALSE;
540   svn_boolean_t skip_children = FALSE;
541   apr_hash_t *dirents = NULL;
542   apr_hash_t *left_props = NULL;
543   svn_diff_source_t *left_source = svn_diff__source_create(eb->revision,
544                                                            scratch_pool);
545   db = make_dir_baton(path, pb, pb->edit_baton, FALSE, SVN_INVALID_REVNUM,
546                       scratch_pool);
547 
548   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(eb->revision));
549 
550   if (eb->cancel_func)
551     SVN_ERR(eb->cancel_func(eb->cancel_baton));
552 
553   SVN_ERR(eb->processor->dir_opened(&db->pdb, &skip, &skip_children,
554                                     path,
555                                     left_source,
556                                     NULL /* right_source */,
557                                     NULL /* copyfrom_source */,
558                                     pb->pdb,
559                                     eb->processor,
560                                     scratch_pool, iterpool));
561 
562   if (!skip || !skip_children)
563     SVN_ERR(svn_ra_get_dir2(eb->ra_session,
564                             skip_children ? NULL : &dirents,
565                             NULL,
566                             skip ? NULL : &left_props,
567                             path,
568                             eb->revision,
569                             SVN_DIRENT_KIND,
570                             scratch_pool));
571 
572   /* The "old" dir will be skipped by the repository report.  If required,
573    * crawl it recursively, diffing each file against the empty file.  This
574    * is a workaround for issue 2333 "'svn diff URL1 URL2' not reverse of
575    * 'svn diff URL2 URL1'". */
576   if (! skip_children)
577     {
578       apr_hash_index_t *hi;
579 
580       for (hi = apr_hash_first(scratch_pool, dirents); hi;
581            hi = apr_hash_next(hi))
582         {
583           const char *child_path;
584           const char *name = apr_hash_this_key(hi);
585           svn_dirent_t *dirent = apr_hash_this_val(hi);
586 
587           svn_pool_clear(iterpool);
588 
589           child_path = svn_relpath_join(path, name, iterpool);
590 
591           if (dirent->kind == svn_node_file)
592             {
593               SVN_ERR(diff_deleted_file(child_path, db, iterpool));
594             }
595           else if (dirent->kind == svn_node_dir)
596             {
597               SVN_ERR(diff_deleted_dir(child_path, db, iterpool));
598             }
599         }
600     }
601 
602   if (! skip)
603     {
604       SVN_ERR(eb->processor->dir_deleted(path,
605                                          left_source,
606                                          left_props,
607                                          db->pdb,
608                                          eb->processor,
609                                          scratch_pool));
610     }
611 
612   SVN_ERR(release_dir(db));
613 
614   svn_pool_destroy(iterpool);
615   return SVN_NO_ERROR;
616 }
617 
618 /* An svn_delta_editor_t function.  */
619 static svn_error_t *
delete_entry(const char * path,svn_revnum_t base_revision,void * parent_baton,apr_pool_t * pool)620 delete_entry(const char *path,
621              svn_revnum_t base_revision,
622              void *parent_baton,
623              apr_pool_t *pool)
624 {
625   struct dir_baton *pb = parent_baton;
626   struct edit_baton *eb = pb->edit_baton;
627   svn_node_kind_t kind;
628   apr_pool_t *scratch_pool;
629 
630   /* Process skips. */
631   if (pb->skip_children)
632     return SVN_NO_ERROR;
633 
634   scratch_pool = svn_pool_create(eb->pool);
635 
636   /* We need to know if this is a directory or a file */
637   SVN_ERR(svn_ra_check_path(eb->ra_session, path, eb->revision, &kind,
638                             scratch_pool));
639 
640   switch (kind)
641     {
642     case svn_node_file:
643       {
644         SVN_ERR(diff_deleted_file(path, pb, scratch_pool));
645         break;
646       }
647     case svn_node_dir:
648       {
649         SVN_ERR(diff_deleted_dir(path, pb, scratch_pool));
650         break;
651       }
652     default:
653       break;
654     }
655 
656   svn_pool_destroy(scratch_pool);
657 
658   return SVN_NO_ERROR;
659 }
660 
661 /* An svn_delta_editor_t function.  */
662 static svn_error_t *
add_directory(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * pool,void ** child_baton)663 add_directory(const char *path,
664               void *parent_baton,
665               const char *copyfrom_path,
666               svn_revnum_t copyfrom_revision,
667               apr_pool_t *pool,
668               void **child_baton)
669 {
670   struct dir_baton *pb = parent_baton;
671   struct edit_baton *eb = pb->edit_baton;
672   struct dir_baton *db;
673 
674   /* ### TODO: support copyfrom? */
675 
676   db = make_dir_baton(path, pb, eb, TRUE, SVN_INVALID_REVNUM, pb->pool);
677   *child_baton = db;
678 
679   /* Skip *everything* within a newly tree-conflicted directory,
680    * and directories the children of which should be skipped. */
681   if (pb->skip_children)
682     {
683       db->skip = TRUE;
684       db->skip_children = TRUE;
685       return SVN_NO_ERROR;
686     }
687 
688   db->right_source = svn_diff__source_create(eb->target_revision,
689                                              db->pool);
690 
691   SVN_ERR(eb->processor->dir_opened(&db->pdb,
692                                     &db->skip,
693                                     &db->skip_children,
694                                     db->path,
695                                     NULL,
696                                     db->right_source,
697                                     NULL /* copyfrom_source */,
698                                     pb->pdb,
699                                     eb->processor,
700                                     db->pool, db->pool));
701 
702   return SVN_NO_ERROR;
703 }
704 
705 /* An svn_delta_editor_t function.  */
706 static svn_error_t *
open_directory(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * pool,void ** child_baton)707 open_directory(const char *path,
708                void *parent_baton,
709                svn_revnum_t base_revision,
710                apr_pool_t *pool,
711                void **child_baton)
712 {
713   struct dir_baton *pb = parent_baton;
714   struct edit_baton *eb = pb->edit_baton;
715   struct dir_baton *db;
716 
717   db = make_dir_baton(path, pb, eb, FALSE, base_revision, pb->pool);
718 
719   *child_baton = db;
720 
721   /* Process Skips. */
722   if (pb->skip_children)
723     {
724       db->skip = TRUE;
725       db->skip_children = TRUE;
726       return SVN_NO_ERROR;
727     }
728 
729   db->left_source = svn_diff__source_create(eb->revision, db->pool);
730   db->right_source = svn_diff__source_create(eb->target_revision, db->pool);
731 
732   SVN_ERR(eb->processor->dir_opened(&db->pdb,
733                                     &db->skip, &db->skip_children,
734                                     path,
735                                     db->left_source,
736                                     db->right_source,
737                                     NULL /* copyfrom */,
738                                     pb ? pb->pdb : NULL,
739                                     eb->processor,
740                                     db->pool, db->pool));
741 
742   return SVN_NO_ERROR;
743 }
744 
745 
746 /* An svn_delta_editor_t function.  */
747 static svn_error_t *
add_file(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * pool,void ** file_baton)748 add_file(const char *path,
749          void *parent_baton,
750          const char *copyfrom_path,
751          svn_revnum_t copyfrom_revision,
752          apr_pool_t *pool,
753          void **file_baton)
754 {
755   struct dir_baton *pb = parent_baton;
756   struct edit_baton *eb = pb->edit_baton;
757   struct file_baton *fb;
758 
759   /* ### TODO: support copyfrom? */
760 
761   fb = make_file_baton(path, pb, TRUE, pb->pool);
762   *file_baton = fb;
763 
764   /* Process Skips. */
765   if (pb->skip_children)
766     {
767       fb->skip = TRUE;
768       return SVN_NO_ERROR;
769     }
770 
771   fb->pristine_props = pb->edit_baton->empty_hash;
772 
773   fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool);
774 
775   SVN_ERR(eb->processor->file_opened(&fb->pfb,
776                                      &fb->skip,
777                                      path,
778                                      NULL,
779                                      fb->right_source,
780                                      NULL /* copy source */,
781                                      pb->pdb,
782                                      eb->processor,
783                                      fb->pool, fb->pool));
784 
785   return SVN_NO_ERROR;
786 }
787 
788 /* An svn_delta_editor_t function.  */
789 static svn_error_t *
open_file(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * pool,void ** file_baton)790 open_file(const char *path,
791           void *parent_baton,
792           svn_revnum_t base_revision,
793           apr_pool_t *pool,
794           void **file_baton)
795 {
796   struct dir_baton *pb = parent_baton;
797   struct file_baton *fb;
798   struct edit_baton *eb = pb->edit_baton;
799   fb = make_file_baton(path, pb, FALSE, pb->pool);
800   *file_baton = fb;
801 
802   /* Process Skips. */
803   if (pb->skip_children)
804     {
805       fb->skip = TRUE;
806       return SVN_NO_ERROR;
807     }
808 
809   fb->base_revision = base_revision;
810 
811   fb->left_source = svn_diff__source_create(eb->revision, fb->pool);
812   fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool);
813 
814   SVN_ERR(eb->processor->file_opened(&fb->pfb,
815                                      &fb->skip,
816                                      path,
817                                      fb->left_source,
818                                      fb->right_source,
819                                      NULL /* copy source */,
820                                      pb->pdb,
821                                      eb->processor,
822                                      fb->pool, fb->pool));
823 
824   return SVN_NO_ERROR;
825 }
826 
827 /* Do the work of applying the text delta.  */
828 static svn_error_t *
window_handler(svn_txdelta_window_t * window,void * window_baton)829 window_handler(svn_txdelta_window_t *window,
830                void *window_baton)
831 {
832   struct file_baton *fb = window_baton;
833 
834   SVN_ERR(fb->apply_handler(window, fb->apply_baton));
835 
836   if (!window)
837     {
838       fb->result_md5_checksum = svn_checksum__from_digest_md5(
839                                         fb->result_digest,
840                                         fb->pool);
841     }
842 
843   return SVN_NO_ERROR;
844 }
845 
846 /* Implements svn_stream_lazyopen_func_t. */
847 static svn_error_t *
lazy_open_source(svn_stream_t ** stream,void * baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)848 lazy_open_source(svn_stream_t **stream,
849                  void *baton,
850                  apr_pool_t *result_pool,
851                  apr_pool_t *scratch_pool)
852 {
853   struct file_baton *fb = baton;
854 
855   SVN_ERR(svn_stream_open_readonly(stream, fb->path_start_revision,
856                                    result_pool, scratch_pool));
857 
858   return SVN_NO_ERROR;
859 }
860 
861 /* Implements svn_stream_lazyopen_func_t. */
862 static svn_error_t *
lazy_open_result(svn_stream_t ** stream,void * baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)863 lazy_open_result(svn_stream_t **stream,
864                  void *baton,
865                  apr_pool_t *result_pool,
866                  apr_pool_t *scratch_pool)
867 {
868   struct file_baton *fb = baton;
869 
870   SVN_ERR(svn_stream_open_unique(stream, &fb->path_end_revision, NULL,
871                                  svn_io_file_del_on_pool_cleanup,
872                                  result_pool, scratch_pool));
873 
874   return SVN_NO_ERROR;
875 }
876 
877 /* An svn_delta_editor_t function.  */
878 static svn_error_t *
apply_textdelta(void * file_baton,const char * base_md5_digest,apr_pool_t * pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)879 apply_textdelta(void *file_baton,
880                 const char *base_md5_digest,
881                 apr_pool_t *pool,
882                 svn_txdelta_window_handler_t *handler,
883                 void **handler_baton)
884 {
885   struct file_baton *fb = file_baton;
886   svn_stream_t *src_stream;
887   svn_stream_t *result_stream;
888   apr_pool_t *scratch_pool = fb->pool;
889 
890   /* Skip *everything* within a newly tree-conflicted directory. */
891   if (fb->skip)
892     {
893       *handler = svn_delta_noop_window_handler;
894       *handler_baton = NULL;
895       return SVN_NO_ERROR;
896     }
897 
898   /* If we're not sending file text, then ignore any that we receive. */
899   if (! fb->edit_baton->text_deltas)
900     {
901       /* Supply valid paths to indicate there is a text change. */
902       SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_start_revision));
903       SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_end_revision));
904 
905       *handler = svn_delta_noop_window_handler;
906       *handler_baton = NULL;
907 
908       return SVN_NO_ERROR;
909     }
910 
911   /* We need the expected pristine file, so go get it */
912   if (!fb->added)
913     SVN_ERR(get_file_from_ra(fb, FALSE, scratch_pool));
914   else
915     SVN_ERR(get_empty_file(fb->edit_baton, &(fb->path_start_revision)));
916 
917   SVN_ERR_ASSERT(fb->path_start_revision != NULL);
918 
919   if (base_md5_digest != NULL)
920     {
921       svn_checksum_t *base_md5_checksum;
922 
923       SVN_ERR(svn_checksum_parse_hex(&base_md5_checksum, svn_checksum_md5,
924                                      base_md5_digest, scratch_pool));
925 
926       if (!svn_checksum_match(base_md5_checksum, fb->start_md5_checksum))
927         return svn_error_trace(svn_checksum_mismatch_err(
928                                       base_md5_checksum,
929                                       fb->start_md5_checksum,
930                                       scratch_pool,
931                                       _("Base checksum mismatch for '%s'"),
932                                       fb->path));
933     }
934 
935   /* Open the file to be used as the base for second revision */
936   src_stream = svn_stream_lazyopen_create(lazy_open_source, fb, TRUE,
937                                           scratch_pool);
938 
939   /* Open the file that will become the second revision after applying the
940      text delta, it starts empty */
941   result_stream = svn_stream_lazyopen_create(lazy_open_result, fb, TRUE,
942                                              scratch_pool);
943 
944   svn_txdelta_apply(src_stream,
945                     result_stream,
946                     fb->result_digest,
947                     fb->path, fb->pool,
948                     &(fb->apply_handler), &(fb->apply_baton));
949 
950   *handler = window_handler;
951   *handler_baton = file_baton;
952 
953   return SVN_NO_ERROR;
954 }
955 
956 /* An svn_delta_editor_t function.  When the file is closed we have a temporary
957  * file containing a pristine version of the repository file. This can
958  * be compared against the working copy.
959  *
960  * ### Ignore TEXT_CHECKSUM for now.  Someday we can use it to verify
961  * ### the integrity of the file being diffed.  Done efficiently, this
962  * ### would probably involve calculating the checksum as the data is
963  * ### received, storing the final checksum in the file_baton, and
964  * ### comparing against it here.
965  */
966 static svn_error_t *
close_file(void * file_baton,const char * expected_md5_digest,apr_pool_t * pool)967 close_file(void *file_baton,
968            const char *expected_md5_digest,
969            apr_pool_t *pool)
970 {
971   struct file_baton *fb = file_baton;
972   struct dir_baton *pb = fb->parent_baton;
973   struct edit_baton *eb = fb->edit_baton;
974   apr_pool_t *scratch_pool;
975 
976   /* Skip *everything* within a newly tree-conflicted directory. */
977   if (fb->skip)
978     {
979       svn_pool_destroy(fb->pool);
980       SVN_ERR(release_dir(pb));
981       return SVN_NO_ERROR;
982     }
983 
984   scratch_pool = fb->pool;
985 
986   if (expected_md5_digest && eb->text_deltas)
987     {
988       svn_checksum_t *expected_md5_checksum;
989 
990       SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
991                                      expected_md5_digest, scratch_pool));
992 
993       if (!svn_checksum_match(expected_md5_checksum, fb->result_md5_checksum))
994         return svn_error_trace(svn_checksum_mismatch_err(
995                                       expected_md5_checksum,
996                                       fb->result_md5_checksum,
997                                       pool,
998                                       _("Checksum mismatch for '%s'"),
999                                       fb->path));
1000     }
1001 
1002   if (fb->added || fb->path_end_revision || fb->has_propchange)
1003     {
1004       apr_hash_t *right_props;
1005 
1006       if (!fb->added && !fb->pristine_props)
1007         {
1008           /* We didn't receive a text change, so we have no pristine props.
1009              Retrieve just the props now. */
1010           SVN_ERR(get_file_from_ra(fb, TRUE, scratch_pool));
1011         }
1012 
1013       if (fb->pristine_props)
1014         SVN_ERR(remove_non_prop_changes(fb->pristine_props, fb->propchanges));
1015 
1016       right_props = svn_prop__patch(fb->pristine_props, fb->propchanges,
1017                                     fb->pool);
1018 
1019       if (fb->added)
1020         SVN_ERR(eb->processor->file_added(fb->path,
1021                                           NULL /* copyfrom_src */,
1022                                           fb->right_source,
1023                                           NULL /* copyfrom_file */,
1024                                           fb->path_end_revision,
1025                                           NULL /* copyfrom_props */,
1026                                           right_props,
1027                                           fb->pfb,
1028                                           eb->processor,
1029                                           fb->pool));
1030       else
1031         SVN_ERR(eb->processor->file_changed(fb->path,
1032                                             fb->left_source,
1033                                             fb->right_source,
1034                                             fb->path_end_revision
1035                                                     ? fb->path_start_revision
1036                                                     : NULL,
1037                                             fb->path_end_revision,
1038                                             fb->pristine_props,
1039                                             right_props,
1040                                             (fb->path_end_revision != NULL),
1041                                             fb->propchanges,
1042                                             fb->pfb,
1043                                             eb->processor,
1044                                             fb->pool));
1045     }
1046 
1047   svn_pool_destroy(fb->pool); /* Destroy file and scratch pool */
1048 
1049   SVN_ERR(release_dir(pb));
1050 
1051   return SVN_NO_ERROR;
1052 }
1053 
1054 /* Report any accumulated prop changes via the 'dir_props_changed' callback,
1055  * and then call the 'dir_closed' callback.  Notify about any deleted paths
1056  * within this directory that have not already been notified, and then about
1057  * this directory itself (unless it was added, in which case the notification
1058  * was done at that time).
1059  *
1060  * An svn_delta_editor_t function.  */
1061 static svn_error_t *
close_directory(void * dir_baton,apr_pool_t * pool)1062 close_directory(void *dir_baton,
1063                 apr_pool_t *pool)
1064 {
1065   struct dir_baton *db = dir_baton;
1066   struct edit_baton *eb = db->edit_baton;
1067   apr_pool_t *scratch_pool;
1068   apr_hash_t *pristine_props;
1069   svn_boolean_t send_changed = FALSE;
1070 
1071   scratch_pool = db->pool;
1072 
1073   if ((db->has_propchange || db->added) && !db->skip)
1074     {
1075       if (db->added)
1076         {
1077           pristine_props = eb->empty_hash;
1078         }
1079       else
1080         {
1081           SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, &pristine_props,
1082                                   db->path, db->base_revision, 0, scratch_pool));
1083         }
1084 
1085       if (db->propchanges->nelts > 0)
1086         {
1087           SVN_ERR(remove_non_prop_changes(pristine_props, db->propchanges));
1088         }
1089 
1090       if (db->propchanges->nelts > 0 || db->added)
1091         {
1092           apr_hash_t *right_props;
1093 
1094           right_props = svn_prop__patch(pristine_props, db->propchanges,
1095                                         scratch_pool);
1096 
1097           if (db->added)
1098             {
1099               SVN_ERR(eb->processor->dir_added(db->path,
1100                                            NULL /* copyfrom */,
1101                                            db->right_source,
1102                                            NULL /* copyfrom props */,
1103                                            right_props,
1104                                            db->pdb,
1105                                            eb->processor,
1106                                            db->pool));
1107             }
1108           else
1109             {
1110               SVN_ERR(eb->processor->dir_changed(db->path,
1111                                                  db->left_source,
1112                                                  db->right_source,
1113                                                  pristine_props,
1114                                                  right_props,
1115                                                  db->propchanges,
1116                                                  db->pdb,
1117                                                  eb->processor,
1118                                                  db->pool));
1119             }
1120 
1121           send_changed = TRUE; /* Skip dir_closed */
1122         }
1123     }
1124 
1125   if (! db->skip && !send_changed)
1126     {
1127       SVN_ERR(eb->processor->dir_closed(db->path,
1128                                         db->left_source,
1129                                         db->right_source,
1130                                         db->pdb,
1131                                         eb->processor,
1132                                         db->pool));
1133     }
1134   SVN_ERR(release_dir(db));
1135 
1136   return SVN_NO_ERROR;
1137 }
1138 
1139 
1140 /* Record a prop change, which we will report later in close_file().
1141  *
1142  * An svn_delta_editor_t function.  */
1143 static svn_error_t *
change_file_prop(void * file_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)1144 change_file_prop(void *file_baton,
1145                  const char *name,
1146                  const svn_string_t *value,
1147                  apr_pool_t *pool)
1148 {
1149   struct file_baton *fb = file_baton;
1150   svn_prop_t *propchange;
1151   svn_prop_kind_t propkind;
1152 
1153   /* Skip *everything* within a newly tree-conflicted directory. */
1154   if (fb->skip)
1155     return SVN_NO_ERROR;
1156 
1157   propkind = svn_property_kind2(name);
1158   if (propkind == svn_prop_wc_kind)
1159     return SVN_NO_ERROR;
1160   else if (propkind == svn_prop_regular_kind)
1161     fb->has_propchange = TRUE;
1162 
1163   propchange = apr_array_push(fb->propchanges);
1164   propchange->name = apr_pstrdup(fb->pool, name);
1165   propchange->value = svn_string_dup(value, fb->pool);
1166 
1167   return SVN_NO_ERROR;
1168 }
1169 
1170 /* Make a note of this prop change, to be reported when the dir is closed.
1171  *
1172  * An svn_delta_editor_t function.  */
1173 static svn_error_t *
change_dir_prop(void * dir_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)1174 change_dir_prop(void *dir_baton,
1175                 const char *name,
1176                 const svn_string_t *value,
1177                 apr_pool_t *pool)
1178 {
1179   struct dir_baton *db = dir_baton;
1180   svn_prop_t *propchange;
1181   svn_prop_kind_t propkind;
1182 
1183   /* Skip *everything* within a newly tree-conflicted directory. */
1184   if (db->skip)
1185     return SVN_NO_ERROR;
1186 
1187   propkind = svn_property_kind2(name);
1188   if (propkind == svn_prop_wc_kind)
1189     return SVN_NO_ERROR;
1190   else if (propkind == svn_prop_regular_kind)
1191     db->has_propchange = TRUE;
1192 
1193   propchange = apr_array_push(db->propchanges);
1194   propchange->name = apr_pstrdup(db->pool, name);
1195   propchange->value = svn_string_dup(value, db->pool);
1196 
1197   return SVN_NO_ERROR;
1198 }
1199 
1200 
1201 /* An svn_delta_editor_t function.  */
1202 static svn_error_t *
close_edit(void * edit_baton,apr_pool_t * pool)1203 close_edit(void *edit_baton,
1204            apr_pool_t *pool)
1205 {
1206   struct edit_baton *eb = edit_baton;
1207 
1208   svn_pool_destroy(eb->pool);
1209 
1210   return SVN_NO_ERROR;
1211 }
1212 
1213 /* Notify that the node at PATH is 'missing'.
1214  * An svn_delta_editor_t function.  */
1215 static svn_error_t *
absent_directory(const char * path,void * parent_baton,apr_pool_t * pool)1216 absent_directory(const char *path,
1217                  void *parent_baton,
1218                  apr_pool_t *pool)
1219 {
1220   struct dir_baton *pb = parent_baton;
1221   struct edit_baton *eb = pb->edit_baton;
1222 
1223   SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool));
1224 
1225   return SVN_NO_ERROR;
1226 }
1227 
1228 
1229 /* Notify that the node at PATH is 'missing'.
1230  * An svn_delta_editor_t function.  */
1231 static svn_error_t *
absent_file(const char * path,void * parent_baton,apr_pool_t * pool)1232 absent_file(const char *path,
1233             void *parent_baton,
1234             apr_pool_t *pool)
1235 {
1236   struct dir_baton *pb = parent_baton;
1237   struct edit_baton *eb = pb->edit_baton;
1238 
1239   SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool));
1240 
1241   return SVN_NO_ERROR;
1242 }
1243 
1244 static svn_error_t *
fetch_kind_func(svn_node_kind_t * kind,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * scratch_pool)1245 fetch_kind_func(svn_node_kind_t *kind,
1246                 void *baton,
1247                 const char *path,
1248                 svn_revnum_t base_revision,
1249                 apr_pool_t *scratch_pool)
1250 {
1251   struct edit_baton *eb = baton;
1252 
1253   if (!SVN_IS_VALID_REVNUM(base_revision))
1254     base_revision = eb->revision;
1255 
1256   SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind,
1257                             scratch_pool));
1258 
1259   return SVN_NO_ERROR;
1260 }
1261 
1262 static svn_error_t *
fetch_props_func(apr_hash_t ** props,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1263 fetch_props_func(apr_hash_t **props,
1264                  void *baton,
1265                  const char *path,
1266                  svn_revnum_t base_revision,
1267                  apr_pool_t *result_pool,
1268                  apr_pool_t *scratch_pool)
1269 {
1270   struct edit_baton *eb = baton;
1271   svn_node_kind_t node_kind;
1272 
1273   if (!SVN_IS_VALID_REVNUM(base_revision))
1274     base_revision = eb->revision;
1275 
1276   SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind,
1277                             scratch_pool));
1278 
1279   if (node_kind == svn_node_file)
1280     {
1281       SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision,
1282                               NULL, NULL, props, result_pool));
1283     }
1284   else if (node_kind == svn_node_dir)
1285     {
1286       apr_array_header_t *tmp_props;
1287 
1288       SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path,
1289                               base_revision, 0 /* Dirent fields */,
1290                               result_pool));
1291       tmp_props = svn_prop_hash_to_array(*props, result_pool);
1292       SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1293                                    result_pool));
1294       *props = svn_prop_array_to_hash(tmp_props, result_pool);
1295     }
1296   else
1297     {
1298       *props = apr_hash_make(result_pool);
1299     }
1300 
1301   return SVN_NO_ERROR;
1302 }
1303 
1304 static svn_error_t *
fetch_base_func(const char ** filename,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1305 fetch_base_func(const char **filename,
1306                 void *baton,
1307                 const char *path,
1308                 svn_revnum_t base_revision,
1309                 apr_pool_t *result_pool,
1310                 apr_pool_t *scratch_pool)
1311 {
1312   struct edit_baton *eb = baton;
1313   svn_stream_t *fstream;
1314   svn_error_t *err;
1315 
1316   if (!SVN_IS_VALID_REVNUM(base_revision))
1317     base_revision = eb->revision;
1318 
1319   SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
1320                                  svn_io_file_del_on_pool_cleanup,
1321                                  result_pool, scratch_pool));
1322 
1323   err = svn_ra_get_file(eb->ra_session, path, base_revision,
1324                         fstream, NULL, NULL, scratch_pool);
1325   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1326     {
1327       svn_error_clear(err);
1328       SVN_ERR(svn_stream_close(fstream));
1329 
1330       *filename = NULL;
1331       return SVN_NO_ERROR;
1332     }
1333   else if (err)
1334     return svn_error_trace(err);
1335 
1336   SVN_ERR(svn_stream_close(fstream));
1337 
1338   return SVN_NO_ERROR;
1339 }
1340 
1341 /* Create a repository diff editor and baton.  */
1342 svn_error_t *
svn_client__get_diff_editor2(const svn_delta_editor_t ** editor,void ** edit_baton,svn_ra_session_t * ra_session,svn_depth_t depth,svn_revnum_t revision,svn_boolean_t text_deltas,const svn_diff_tree_processor_t * processor,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool)1343 svn_client__get_diff_editor2(const svn_delta_editor_t **editor,
1344                              void **edit_baton,
1345                              svn_ra_session_t *ra_session,
1346                              svn_depth_t depth,
1347                              svn_revnum_t revision,
1348                              svn_boolean_t text_deltas,
1349                              const svn_diff_tree_processor_t *processor,
1350                              svn_cancel_func_t cancel_func,
1351                              void *cancel_baton,
1352                              apr_pool_t *result_pool)
1353 {
1354   apr_pool_t *editor_pool = svn_pool_create(result_pool);
1355   svn_delta_editor_t *tree_editor = svn_delta_default_editor(editor_pool);
1356   struct edit_baton *eb = apr_pcalloc(editor_pool, sizeof(*eb));
1357   svn_delta_shim_callbacks_t *shim_callbacks =
1358                                 svn_delta_shim_callbacks_default(editor_pool);
1359 
1360   eb->pool = editor_pool;
1361   eb->depth = depth;
1362 
1363   eb->processor = processor;
1364 
1365   eb->ra_session = ra_session;
1366 
1367   eb->revision = revision;
1368   eb->target_revision = SVN_INVALID_REVNUM;
1369   eb->empty_file = NULL;
1370   eb->empty_hash = apr_hash_make(eb->pool);
1371   eb->text_deltas = text_deltas;
1372   eb->cancel_func = cancel_func;
1373   eb->cancel_baton = cancel_baton;
1374 
1375   tree_editor->set_target_revision = set_target_revision;
1376   tree_editor->open_root = open_root;
1377   tree_editor->delete_entry = delete_entry;
1378   tree_editor->add_directory = add_directory;
1379   tree_editor->open_directory = open_directory;
1380   tree_editor->add_file = add_file;
1381   tree_editor->open_file = open_file;
1382   tree_editor->apply_textdelta = apply_textdelta;
1383   tree_editor->close_file = close_file;
1384   tree_editor->close_directory = close_directory;
1385   tree_editor->change_file_prop = change_file_prop;
1386   tree_editor->change_dir_prop = change_dir_prop;
1387   tree_editor->close_edit = close_edit;
1388   tree_editor->absent_directory = absent_directory;
1389   tree_editor->absent_file = absent_file;
1390 
1391   SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1392                                             tree_editor, eb,
1393                                             editor, edit_baton,
1394                                             eb->pool));
1395 
1396   shim_callbacks->fetch_kind_func = fetch_kind_func;
1397   shim_callbacks->fetch_props_func = fetch_props_func;
1398   shim_callbacks->fetch_base_func = fetch_base_func;
1399   shim_callbacks->fetch_baton = eb;
1400 
1401   SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1402                                    NULL, NULL, shim_callbacks,
1403                                    result_pool, result_pool));
1404 
1405   return SVN_NO_ERROR;
1406 }
1407