1/*
2 * diff_tree.c :  default diff tree processor
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#include <apr.h>
25#include <apr_pools.h>
26#include <apr_general.h>
27
28#include <assert.h>
29
30#include "svn_dirent_uri.h"
31#include "svn_error.h"
32#include "svn_io.h"
33#include "svn_pools.h"
34#include "svn_props.h"
35#include "svn_types.h"
36
37#include "private/svn_diff_tree.h"
38#include "svn_private_config.h"
39
40typedef struct tree_processor_t
41{
42  svn_diff_tree_processor_t tp;
43
44  /* void *future_extension */
45} tree_processor_t;
46
47
48static svn_error_t *
49default_dir_opened(void **new_dir_baton,
50                   svn_boolean_t *skip,
51                   svn_boolean_t *skip_children,
52                   const char *relpath,
53                   const svn_diff_source_t *left_source,
54                   const svn_diff_source_t *right_source,
55                   const svn_diff_source_t *copyfrom_source,
56                   void *parent_dir_baton,
57                   const svn_diff_tree_processor_t *processor,
58                   apr_pool_t *result_pool,
59                   apr_pool_t *scratch_pool)
60{
61  *new_dir_baton = NULL;
62  return SVN_NO_ERROR;
63}
64
65static svn_error_t *
66default_dir_added(const char *relpath,
67                  const svn_diff_source_t *copyfrom_source,
68                  const svn_diff_source_t *right_source,
69                  /*const*/ apr_hash_t *copyfrom_props,
70                  /*const*/ apr_hash_t *right_props,
71                  void *dir_baton,
72                  const svn_diff_tree_processor_t *processor,
73                  apr_pool_t *scratch_pool)
74{
75  SVN_ERR(processor->dir_closed(relpath, NULL, right_source,
76                                dir_baton, processor,
77                                scratch_pool));
78
79  return SVN_NO_ERROR;
80}
81
82static svn_error_t *
83default_dir_deleted(const char *relpath,
84                    const svn_diff_source_t *left_source,
85                    /*const*/ apr_hash_t *left_props,
86                    void *dir_baton,
87                    const svn_diff_tree_processor_t *processor,
88                    apr_pool_t *scratch_pool)
89{
90  SVN_ERR(processor->dir_closed(relpath, left_source, NULL,
91                                dir_baton, processor,
92                                scratch_pool));
93  return SVN_NO_ERROR;
94}
95
96static svn_error_t *
97default_dir_changed(const char *relpath,
98                    const svn_diff_source_t *left_source,
99                    const svn_diff_source_t *right_source,
100                    /*const*/ apr_hash_t *left_props,
101                    /*const*/ apr_hash_t *right_props,
102                    const apr_array_header_t *prop_changes,
103                    void *dir_baton,
104                    const struct svn_diff_tree_processor_t *processor,
105                    apr_pool_t *scratch_pool)
106{
107  SVN_ERR(processor->dir_closed(relpath,
108                                left_source, right_source,
109                                dir_baton,
110                                processor, scratch_pool));
111  return SVN_NO_ERROR;
112}
113
114static svn_error_t *
115default_dir_closed(const char *relpath,
116                   const svn_diff_source_t *left_source,
117                   const svn_diff_source_t *right_source,
118                   void *dir_baton,
119                   const svn_diff_tree_processor_t *processor,
120                   apr_pool_t *scratch_pool)
121{
122  return SVN_NO_ERROR;
123}
124
125static svn_error_t *
126default_file_opened(void **new_file_baton,
127                    svn_boolean_t *skip,
128                    const char *relpath,
129                    const svn_diff_source_t *left_source,
130                    const svn_diff_source_t *right_source,
131                    const svn_diff_source_t *copyfrom_source,
132                    void *dir_baton,
133                    const svn_diff_tree_processor_t *processor,
134                    apr_pool_t *result_pool,
135                    apr_pool_t *scratch_pool)
136{
137  *new_file_baton = dir_baton;
138  return SVN_NO_ERROR;
139}
140
141static svn_error_t *
142default_file_added(const char *relpath,
143                   const svn_diff_source_t *copyfrom_source,
144                   const svn_diff_source_t *right_source,
145                   const char *copyfrom_file,
146                   const char *right_file,
147                   /*const*/ apr_hash_t *copyfrom_props,
148                   /*const*/ apr_hash_t *right_props,
149                   void *file_baton,
150                   const svn_diff_tree_processor_t *processor,
151                   apr_pool_t *scratch_pool)
152{
153  SVN_ERR(processor->file_closed(relpath,
154                                 NULL, right_source,
155                                 file_baton, processor, scratch_pool));
156  return SVN_NO_ERROR;
157}
158
159static svn_error_t *
160default_file_deleted(const char *relpath,
161                     const svn_diff_source_t *left_source,
162                     const char *left_file,
163                     /*const*/ apr_hash_t *left_props,
164                     void *file_baton,
165                     const svn_diff_tree_processor_t *processor,
166                     apr_pool_t *scratch_pool)
167{
168  SVN_ERR(processor->file_closed(relpath,
169                                 left_source, NULL,
170                                 file_baton, processor, scratch_pool));
171  return SVN_NO_ERROR;
172}
173
174static svn_error_t *
175default_file_changed(const char *relpath,
176                     const svn_diff_source_t *left_source,
177                     const svn_diff_source_t *right_source,
178                     const char *left_file,
179                     const char *right_file,
180                     /*const*/ apr_hash_t *left_props,
181                     /*const*/ apr_hash_t *right_props,
182                     svn_boolean_t file_modified,
183                     const apr_array_header_t *prop_changes,
184                     void *file_baton,
185                     const svn_diff_tree_processor_t *processor,
186                     apr_pool_t *scratch_pool)
187{
188  SVN_ERR(processor->file_closed(relpath,
189                                 left_source, right_source,
190                                 file_baton, processor, scratch_pool));
191  return SVN_NO_ERROR;
192}
193
194static svn_error_t *
195default_file_closed(const char *relpath,
196                    const svn_diff_source_t *left_source,
197                    const svn_diff_source_t *right_source,
198                    void *file_baton,
199                    const svn_diff_tree_processor_t *processor,
200                    apr_pool_t *scratch_pool)
201{
202  return SVN_NO_ERROR;
203}
204
205static svn_error_t *
206default_node_absent(const char *relpath,
207                    void *dir_baton,
208                    const svn_diff_tree_processor_t *processor,
209                    apr_pool_t *scratch_pool)
210{
211  return SVN_NO_ERROR;
212}
213
214svn_diff_tree_processor_t *
215svn_diff__tree_processor_create(void *baton,
216                                apr_pool_t *result_pool)
217{
218  tree_processor_t *wrapper;
219  wrapper = apr_pcalloc(result_pool, sizeof(*wrapper));
220
221  wrapper->tp.baton        = baton;
222
223  wrapper->tp.dir_opened   = default_dir_opened;
224  wrapper->tp.dir_added    = default_dir_added;
225  wrapper->tp.dir_deleted  = default_dir_deleted;
226  wrapper->tp.dir_changed  = default_dir_changed;
227  wrapper->tp.dir_closed   = default_dir_closed;
228
229  wrapper->tp.file_opened   = default_file_opened;
230  wrapper->tp.file_added    = default_file_added;
231  wrapper->tp.file_deleted  = default_file_deleted;
232  wrapper->tp.file_changed  = default_file_changed;
233  wrapper->tp.file_closed   = default_file_closed;
234
235  wrapper->tp.node_absent   = default_node_absent;
236
237
238  return &wrapper->tp;
239}
240
241struct reverse_tree_baton_t
242{
243  const svn_diff_tree_processor_t *processor;
244  const char *prefix_relpath;
245};
246
247static svn_error_t *
248reverse_dir_opened(void **new_dir_baton,
249                   svn_boolean_t *skip,
250                   svn_boolean_t *skip_children,
251                   const char *relpath,
252                   const svn_diff_source_t *left_source,
253                   const svn_diff_source_t *right_source,
254                   const svn_diff_source_t *copyfrom_source,
255                   void *parent_dir_baton,
256                   const svn_diff_tree_processor_t *processor,
257                   apr_pool_t *result_pool,
258                   apr_pool_t *scratch_pool)
259{
260  struct reverse_tree_baton_t *rb = processor->baton;
261
262  if (rb->prefix_relpath)
263    relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
264
265  SVN_ERR(rb->processor->dir_opened(new_dir_baton, skip, skip_children,
266                                    relpath,
267                                    right_source, left_source,
268                                    NULL /* copyfrom */,
269                                    parent_dir_baton,
270                                    rb->processor,
271                                    result_pool, scratch_pool));
272  return SVN_NO_ERROR;
273}
274
275static svn_error_t *
276reverse_dir_added(const char *relpath,
277                  const svn_diff_source_t *copyfrom_source,
278                  const svn_diff_source_t *right_source,
279                  /*const*/ apr_hash_t *copyfrom_props,
280                  /*const*/ apr_hash_t *right_props,
281                  void *dir_baton,
282                  const svn_diff_tree_processor_t *processor,
283                  apr_pool_t *scratch_pool)
284{
285  struct reverse_tree_baton_t *rb = processor->baton;
286
287  if (rb->prefix_relpath)
288    relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
289
290  SVN_ERR(rb->processor->dir_deleted(relpath,
291                                     right_source,
292                                     right_props,
293                                     dir_baton,
294                                     rb->processor,
295                                     scratch_pool));
296
297  return SVN_NO_ERROR;
298}
299
300static svn_error_t *
301reverse_dir_deleted(const char *relpath,
302                    const svn_diff_source_t *left_source,
303                    /*const*/ apr_hash_t *left_props,
304                    void *dir_baton,
305                    const svn_diff_tree_processor_t *processor,
306                    apr_pool_t *scratch_pool)
307{
308  struct reverse_tree_baton_t *rb = processor->baton;
309
310  if (rb->prefix_relpath)
311    relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
312
313  SVN_ERR(rb->processor->dir_added(relpath,
314                                   NULL,
315                                   left_source,
316                                   NULL,
317                                   left_props,
318                                   dir_baton,
319                                   rb->processor,
320                                   scratch_pool));
321  return SVN_NO_ERROR;
322}
323
324static svn_error_t *
325reverse_dir_changed(const char *relpath,
326                    const svn_diff_source_t *left_source,
327                    const svn_diff_source_t *right_source,
328                    /*const*/ apr_hash_t *left_props,
329                    /*const*/ apr_hash_t *right_props,
330                    const apr_array_header_t *prop_changes,
331                    void *dir_baton,
332                    const struct svn_diff_tree_processor_t *processor,
333                    apr_pool_t *scratch_pool)
334{
335  struct reverse_tree_baton_t *rb = processor->baton;
336  apr_array_header_t *reversed_prop_changes = NULL;
337
338  if (rb->prefix_relpath)
339    relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
340
341  if (prop_changes)
342    {
343      SVN_ERR_ASSERT(left_props != NULL && right_props != NULL);
344      SVN_ERR(svn_prop_diffs(&reversed_prop_changes, left_props, right_props,
345                             scratch_pool));
346    }
347
348  SVN_ERR(rb->processor->dir_changed(relpath,
349                                     right_source,
350                                     left_source,
351                                     right_props,
352                                     left_props,
353                                     reversed_prop_changes,
354                                     dir_baton,
355                                     rb->processor,
356                                     scratch_pool));
357  return SVN_NO_ERROR;
358}
359
360static svn_error_t *
361reverse_dir_closed(const char *relpath,
362                   const svn_diff_source_t *left_source,
363                   const svn_diff_source_t *right_source,
364                   void *dir_baton,
365                   const svn_diff_tree_processor_t *processor,
366                   apr_pool_t *scratch_pool)
367{
368  struct reverse_tree_baton_t *rb = processor->baton;
369
370  if (rb->prefix_relpath)
371    relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
372
373  SVN_ERR(rb->processor->dir_closed(relpath,
374                                    right_source,
375                                    left_source,
376                                    dir_baton,
377                                    rb->processor,
378                                    scratch_pool));
379  return SVN_NO_ERROR;
380}
381
382static svn_error_t *
383reverse_file_opened(void **new_file_baton,
384                    svn_boolean_t *skip,
385                    const char *relpath,
386                    const svn_diff_source_t *left_source,
387                    const svn_diff_source_t *right_source,
388                    const svn_diff_source_t *copyfrom_source,
389                    void *dir_baton,
390                    const svn_diff_tree_processor_t *processor,
391                    apr_pool_t *result_pool,
392                    apr_pool_t *scratch_pool)
393{
394  struct reverse_tree_baton_t *rb = processor->baton;
395
396  if (rb->prefix_relpath)
397    relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
398
399  SVN_ERR(rb->processor->file_opened(new_file_baton,
400                                     skip,
401                                     relpath,
402                                     right_source,
403                                     left_source,
404                                     NULL /* copy_from */,
405                                     dir_baton,
406                                     rb->processor,
407                                     result_pool,
408                                     scratch_pool));
409  return SVN_NO_ERROR;
410}
411
412static svn_error_t *
413reverse_file_added(const char *relpath,
414                   const svn_diff_source_t *copyfrom_source,
415                   const svn_diff_source_t *right_source,
416                   const char *copyfrom_file,
417                   const char *right_file,
418                   /*const*/ apr_hash_t *copyfrom_props,
419                   /*const*/ apr_hash_t *right_props,
420                   void *file_baton,
421                   const svn_diff_tree_processor_t *processor,
422                   apr_pool_t *scratch_pool)
423{
424  struct reverse_tree_baton_t *rb = processor->baton;
425
426  if (rb->prefix_relpath)
427    relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
428
429  SVN_ERR(rb->processor->file_deleted(relpath,
430                                      right_source,
431                                      right_file,
432                                      right_props,
433                                      file_baton,
434                                      rb->processor,
435                                      scratch_pool));
436  return SVN_NO_ERROR;
437}
438
439static svn_error_t *
440reverse_file_deleted(const char *relpath,
441                     const svn_diff_source_t *left_source,
442                     const char *left_file,
443                     /*const*/ apr_hash_t *left_props,
444                     void *file_baton,
445                     const svn_diff_tree_processor_t *processor,
446                     apr_pool_t *scratch_pool)
447{
448  struct reverse_tree_baton_t *rb = processor->baton;
449
450  if (rb->prefix_relpath)
451    relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
452
453  SVN_ERR(rb->processor->file_added(relpath,
454                                    NULL /* copyfrom src */,
455                                    left_source,
456                                    NULL /* copyfrom file */,
457                                    left_file,
458                                    NULL /* copyfrom props */,
459                                    left_props,
460                                    file_baton,
461                                    rb->processor,
462                                    scratch_pool));
463  return SVN_NO_ERROR;
464}
465
466static svn_error_t *
467reverse_file_changed(const char *relpath,
468                     const svn_diff_source_t *left_source,
469                     const svn_diff_source_t *right_source,
470                     const char *left_file,
471                     const char *right_file,
472                     /*const*/ apr_hash_t *left_props,
473                     /*const*/ apr_hash_t *right_props,
474                     svn_boolean_t file_modified,
475                     const apr_array_header_t *prop_changes,
476                     void *file_baton,
477                     const svn_diff_tree_processor_t *processor,
478                     apr_pool_t *scratch_pool)
479{
480  struct reverse_tree_baton_t *rb = processor->baton;
481  apr_array_header_t *reversed_prop_changes = NULL;
482
483  if (rb->prefix_relpath)
484    relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
485
486  if (prop_changes)
487    {
488      SVN_ERR_ASSERT(left_props != NULL && right_props != NULL);
489      SVN_ERR(svn_prop_diffs(&reversed_prop_changes, left_props, right_props,
490                             scratch_pool));
491    }
492
493  SVN_ERR(rb->processor->file_changed(relpath,
494                                      right_source,
495                                      left_source,
496                                      right_file,
497                                      left_file,
498                                      right_props,
499                                      left_props,
500                                      file_modified,
501                                      reversed_prop_changes,
502                                      file_baton,
503                                      rb->processor,
504                                      scratch_pool));
505  return SVN_NO_ERROR;
506}
507
508static svn_error_t *
509reverse_file_closed(const char *relpath,
510                    const svn_diff_source_t *left_source,
511                    const svn_diff_source_t *right_source,
512                    void *file_baton,
513                    const svn_diff_tree_processor_t *processor,
514                    apr_pool_t *scratch_pool)
515{
516  struct reverse_tree_baton_t *rb = processor->baton;
517
518  if (rb->prefix_relpath)
519    relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
520
521  SVN_ERR(rb->processor->file_closed(relpath,
522                                     right_source,
523                                     left_source,
524                                     file_baton,
525                                     rb->processor,
526                                     scratch_pool));
527
528  return SVN_NO_ERROR;
529}
530
531static svn_error_t *
532reverse_node_absent(const char *relpath,
533                    void *dir_baton,
534                    const svn_diff_tree_processor_t *processor,
535                    apr_pool_t *scratch_pool)
536{
537  struct reverse_tree_baton_t *rb = processor->baton;
538
539  if (rb->prefix_relpath)
540    relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
541
542  SVN_ERR(rb->processor->node_absent(relpath,
543                                    dir_baton,
544                                    rb->processor,
545                                    scratch_pool));
546  return SVN_NO_ERROR;
547}
548
549
550const svn_diff_tree_processor_t *
551svn_diff__tree_processor_reverse_create(const svn_diff_tree_processor_t * processor,
552                                        const char *prefix_relpath,
553                                        apr_pool_t *result_pool)
554{
555  struct reverse_tree_baton_t *rb;
556  svn_diff_tree_processor_t *reverse;
557
558  rb = apr_pcalloc(result_pool, sizeof(*rb));
559  rb->processor = processor;
560  if (prefix_relpath)
561    rb->prefix_relpath = apr_pstrdup(result_pool, prefix_relpath);
562
563  reverse = svn_diff__tree_processor_create(rb, result_pool);
564
565  reverse->dir_opened   = reverse_dir_opened;
566  reverse->dir_added    = reverse_dir_added;
567  reverse->dir_deleted  = reverse_dir_deleted;
568  reverse->dir_changed  = reverse_dir_changed;
569  reverse->dir_closed   = reverse_dir_closed;
570
571  reverse->file_opened   = reverse_file_opened;
572  reverse->file_added    = reverse_file_added;
573  reverse->file_deleted  = reverse_file_deleted;
574  reverse->file_changed  = reverse_file_changed;
575  reverse->file_closed   = reverse_file_closed;
576
577  reverse->node_absent   = reverse_node_absent;
578
579  return reverse;
580}
581
582struct filter_tree_baton_t
583{
584  const svn_diff_tree_processor_t *processor;
585  const char *prefix_relpath;
586};
587
588static svn_error_t *
589filter_dir_opened(void **new_dir_baton,
590                  svn_boolean_t *skip,
591                  svn_boolean_t *skip_children,
592                  const char *relpath,
593                  const svn_diff_source_t *left_source,
594                  const svn_diff_source_t *right_source,
595                  const svn_diff_source_t *copyfrom_source,
596                  void *parent_dir_baton,
597                  const svn_diff_tree_processor_t *processor,
598                  apr_pool_t *result_pool,
599                  apr_pool_t *scratch_pool)
600{
601  struct filter_tree_baton_t *fb = processor->baton;
602
603  relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
604
605  if (! relpath)
606    {
607      /* Skip work for this, but NOT for DESCENDANTS */
608      *skip = TRUE;
609      return SVN_NO_ERROR;
610    }
611
612  SVN_ERR(fb->processor->dir_opened(new_dir_baton, skip, skip_children,
613                                    relpath,
614                                    left_source, right_source,
615                                    copyfrom_source,
616                                    parent_dir_baton,
617                                    fb->processor,
618                                    result_pool, scratch_pool));
619  return SVN_NO_ERROR;
620}
621
622static svn_error_t *
623filter_dir_added(const char *relpath,
624                 const svn_diff_source_t *copyfrom_source,
625                 const svn_diff_source_t *right_source,
626                 /*const*/ apr_hash_t *copyfrom_props,
627                 /*const*/ apr_hash_t *right_props,
628                 void *dir_baton,
629                 const svn_diff_tree_processor_t *processor,
630                 apr_pool_t *scratch_pool)
631{
632  struct filter_tree_baton_t *fb = processor->baton;
633
634  relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
635  assert(relpath != NULL); /* Driver error */
636
637  SVN_ERR(fb->processor->dir_added(relpath,
638                                   copyfrom_source,
639                                   right_source,
640                                   copyfrom_props,
641                                   right_props,
642                                   dir_baton,
643                                   fb->processor,
644                                   scratch_pool));
645
646  return SVN_NO_ERROR;
647}
648
649static svn_error_t *
650filter_dir_deleted(const char *relpath,
651                   const svn_diff_source_t *left_source,
652                   /*const*/ apr_hash_t *left_props,
653                   void *dir_baton,
654                   const svn_diff_tree_processor_t *processor,
655                   apr_pool_t *scratch_pool)
656{
657  struct filter_tree_baton_t *fb = processor->baton;
658
659  relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
660  assert(relpath != NULL); /* Driver error */
661
662  SVN_ERR(fb->processor->dir_deleted(relpath,
663                                     left_source,
664                                     left_props,
665                                     dir_baton,
666                                     fb->processor,
667                                     scratch_pool));
668
669  return SVN_NO_ERROR;
670}
671
672static svn_error_t *
673filter_dir_changed(const char *relpath,
674                   const svn_diff_source_t *left_source,
675                   const svn_diff_source_t *right_source,
676                   /*const*/ apr_hash_t *left_props,
677                   /*const*/ apr_hash_t *right_props,
678                   const apr_array_header_t *prop_changes,
679                   void *dir_baton,
680                   const struct svn_diff_tree_processor_t *processor,
681                   apr_pool_t *scratch_pool)
682{
683  struct filter_tree_baton_t *fb = processor->baton;
684
685  relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
686  assert(relpath != NULL); /* Driver error */
687
688  SVN_ERR(fb->processor->dir_changed(relpath,
689                                     left_source,
690                                     right_source,
691                                     left_props,
692                                     right_props,
693                                     prop_changes,
694                                     dir_baton,
695                                     fb->processor,
696                                     scratch_pool));
697  return SVN_NO_ERROR;
698}
699
700static svn_error_t *
701filter_dir_closed(const char *relpath,
702                  const svn_diff_source_t *left_source,
703                  const svn_diff_source_t *right_source,
704                  void *dir_baton,
705                  const svn_diff_tree_processor_t *processor,
706                  apr_pool_t *scratch_pool)
707{
708  struct filter_tree_baton_t *fb = processor->baton;
709
710  relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
711  assert(relpath != NULL); /* Driver error */
712
713  SVN_ERR(fb->processor->dir_closed(relpath,
714                                    left_source,
715                                    right_source,
716                                    dir_baton,
717                                    fb->processor,
718                                    scratch_pool));
719  return SVN_NO_ERROR;
720}
721
722static svn_error_t *
723filter_file_opened(void **new_file_baton,
724                   svn_boolean_t *skip,
725                   const char *relpath,
726                   const svn_diff_source_t *left_source,
727                   const svn_diff_source_t *right_source,
728                   const svn_diff_source_t *copyfrom_source,
729                   void *dir_baton,
730                   const svn_diff_tree_processor_t *processor,
731                   apr_pool_t *result_pool,
732                   apr_pool_t *scratch_pool)
733{
734  struct filter_tree_baton_t *fb = processor->baton;
735
736  relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
737
738  if (! relpath)
739    {
740      *skip = TRUE;
741      return SVN_NO_ERROR;
742    }
743
744  SVN_ERR(fb->processor->file_opened(new_file_baton,
745                                     skip,
746                                     relpath,
747                                     left_source,
748                                     right_source,
749                                     copyfrom_source,
750                                     dir_baton,
751                                     fb->processor,
752                                     result_pool,
753                                     scratch_pool));
754  return SVN_NO_ERROR;
755}
756
757static svn_error_t *
758filter_file_added(const char *relpath,
759                  const svn_diff_source_t *copyfrom_source,
760                  const svn_diff_source_t *right_source,
761                  const char *copyfrom_file,
762                  const char *right_file,
763                  /*const*/ apr_hash_t *copyfrom_props,
764                  /*const*/ apr_hash_t *right_props,
765                  void *file_baton,
766                  const svn_diff_tree_processor_t *processor,
767                  apr_pool_t *scratch_pool)
768{
769  struct filter_tree_baton_t *fb = processor->baton;
770
771  relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
772  assert(relpath != NULL); /* Driver error */
773
774  SVN_ERR(fb->processor->file_added(relpath,
775                                    copyfrom_source,
776                                    right_source,
777                                    copyfrom_file,
778                                    right_file,
779                                    copyfrom_props,
780                                    right_props,
781                                    file_baton,
782                                    fb->processor,
783                                    scratch_pool));
784  return SVN_NO_ERROR;
785}
786
787static svn_error_t *
788filter_file_deleted(const char *relpath,
789                    const svn_diff_source_t *left_source,
790                    const char *left_file,
791                    /*const*/ apr_hash_t *left_props,
792                    void *file_baton,
793                    const svn_diff_tree_processor_t *processor,
794                    apr_pool_t *scratch_pool)
795{
796  struct filter_tree_baton_t *fb = processor->baton;
797
798  relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
799  assert(relpath != NULL); /* Driver error */
800
801  SVN_ERR(fb->processor->file_deleted(relpath,
802                                      left_source,
803                                      left_file,
804                                      left_props,
805                                      file_baton,
806                                      fb->processor,
807                                      scratch_pool));
808
809  return SVN_NO_ERROR;
810}
811
812static svn_error_t *
813filter_file_changed(const char *relpath,
814                    const svn_diff_source_t *left_source,
815                    const svn_diff_source_t *right_source,
816                    const char *left_file,
817                    const char *right_file,
818                    /*const*/ apr_hash_t *left_props,
819                    /*const*/ apr_hash_t *right_props,
820                    svn_boolean_t file_modified,
821                    const apr_array_header_t *prop_changes,
822                    void *file_baton,
823                    const svn_diff_tree_processor_t *processor,
824                    apr_pool_t *scratch_pool)
825{
826  struct filter_tree_baton_t *fb = processor->baton;
827
828  relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
829  assert(relpath != NULL); /* Driver error */
830
831  SVN_ERR(fb->processor->file_changed(relpath,
832                                      left_source,
833                                      right_source,
834                                      left_file,
835                                      right_file,
836                                      left_props,
837                                      right_props,
838                                      file_modified,
839                                      prop_changes,
840                                      file_baton,
841                                      fb->processor,
842                                      scratch_pool));
843  return SVN_NO_ERROR;
844}
845
846static svn_error_t *
847filter_file_closed(const char *relpath,
848                   const svn_diff_source_t *left_source,
849                   const svn_diff_source_t *right_source,
850                   void *file_baton,
851                   const svn_diff_tree_processor_t *processor,
852                   apr_pool_t *scratch_pool)
853{
854  struct filter_tree_baton_t *fb = processor->baton;
855
856  relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
857  assert(relpath != NULL); /* Driver error */
858
859  SVN_ERR(fb->processor->file_closed(relpath,
860                                     left_source,
861                                     right_source,
862                                     file_baton,
863                                     fb->processor,
864                                     scratch_pool));
865
866  return SVN_NO_ERROR;
867}
868
869static svn_error_t *
870filter_node_absent(const char *relpath,
871                   void *dir_baton,
872                   const svn_diff_tree_processor_t *processor,
873                   apr_pool_t *scratch_pool)
874{
875  struct filter_tree_baton_t *fb = processor->baton;
876
877  relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
878  assert(relpath != NULL); /* Driver error */
879
880  SVN_ERR(fb->processor->node_absent(relpath,
881                                    dir_baton,
882                                    fb->processor,
883                                    scratch_pool));
884  return SVN_NO_ERROR;
885}
886
887
888const svn_diff_tree_processor_t *
889svn_diff__tree_processor_filter_create(const svn_diff_tree_processor_t * processor,
890                                        const char *prefix_relpath,
891                                        apr_pool_t *result_pool)
892{
893  struct filter_tree_baton_t *fb;
894  svn_diff_tree_processor_t *filter;
895
896  fb = apr_pcalloc(result_pool, sizeof(*fb));
897  fb->processor = processor;
898  if (prefix_relpath)
899    fb->prefix_relpath = apr_pstrdup(result_pool, prefix_relpath);
900
901  filter = svn_diff__tree_processor_create(fb, result_pool);
902
903  filter->dir_opened   = filter_dir_opened;
904  filter->dir_added    = filter_dir_added;
905  filter->dir_deleted  = filter_dir_deleted;
906  filter->dir_changed  = filter_dir_changed;
907  filter->dir_closed   = filter_dir_closed;
908
909  filter->file_opened   = filter_file_opened;
910  filter->file_added    = filter_file_added;
911  filter->file_deleted  = filter_file_deleted;
912  filter->file_changed  = filter_file_changed;
913  filter->file_closed   = filter_file_closed;
914
915  filter->node_absent   = filter_node_absent;
916
917  return filter;
918}
919
920struct copy_as_changed_baton_t
921{
922  const svn_diff_tree_processor_t *processor;
923};
924
925static svn_error_t *
926copy_as_changed_dir_opened(void **new_dir_baton,
927                           svn_boolean_t *skip,
928                           svn_boolean_t *skip_children,
929                           const char *relpath,
930                           const svn_diff_source_t *left_source,
931                           const svn_diff_source_t *right_source,
932                           const svn_diff_source_t *copyfrom_source,
933                           void *parent_dir_baton,
934                           const svn_diff_tree_processor_t *processor,
935                           apr_pool_t *result_pool,
936                           apr_pool_t *scratch_pool)
937{
938  struct copy_as_changed_baton_t *cb = processor->baton;
939
940  if (!left_source && copyfrom_source)
941    {
942      assert(right_source != NULL);
943
944      left_source = copyfrom_source;
945      copyfrom_source = NULL;
946    }
947
948  SVN_ERR(cb->processor->dir_opened(new_dir_baton, skip, skip_children,
949                                    relpath,
950                                    left_source, right_source,
951                                    copyfrom_source,
952                                    parent_dir_baton,
953                                    cb->processor,
954                                    result_pool, scratch_pool));
955  return SVN_NO_ERROR;
956}
957
958static svn_error_t *
959copy_as_changed_dir_added(const char *relpath,
960                          const svn_diff_source_t *copyfrom_source,
961                          const svn_diff_source_t *right_source,
962                          /*const*/ apr_hash_t *copyfrom_props,
963                          /*const*/ apr_hash_t *right_props,
964                          void *dir_baton,
965                          const svn_diff_tree_processor_t *processor,
966                          apr_pool_t *scratch_pool)
967{
968  struct copy_as_changed_baton_t *cb = processor->baton;
969
970  if (copyfrom_source)
971    {
972      apr_array_header_t *propchanges;
973      SVN_ERR(svn_prop_diffs(&propchanges, right_props, copyfrom_props,
974                             scratch_pool));
975      SVN_ERR(cb->processor->dir_changed(relpath,
976                                         copyfrom_source,
977                                         right_source,
978                                         copyfrom_props,
979                                         right_props,
980                                         propchanges,
981                                         dir_baton,
982                                         cb->processor,
983                                         scratch_pool));
984    }
985  else
986    {
987      SVN_ERR(cb->processor->dir_added(relpath,
988                                       copyfrom_source,
989                                       right_source,
990                                       copyfrom_props,
991                                       right_props,
992                                       dir_baton,
993                                       cb->processor,
994                                       scratch_pool));
995    }
996
997  return SVN_NO_ERROR;
998}
999
1000static svn_error_t *
1001copy_as_changed_dir_deleted(const char *relpath,
1002                            const svn_diff_source_t *left_source,
1003                            /*const*/ apr_hash_t *left_props,
1004                            void *dir_baton,
1005                            const svn_diff_tree_processor_t *processor,
1006                            apr_pool_t *scratch_pool)
1007{
1008  struct copy_as_changed_baton_t *cb = processor->baton;
1009
1010  SVN_ERR(cb->processor->dir_deleted(relpath,
1011                                     left_source,
1012                                     left_props,
1013                                     dir_baton,
1014                                     cb->processor,
1015                                     scratch_pool));
1016
1017  return SVN_NO_ERROR;
1018}
1019
1020static svn_error_t *
1021copy_as_changed_dir_changed(const char *relpath,
1022                            const svn_diff_source_t *left_source,
1023                            const svn_diff_source_t *right_source,
1024                            /*const*/ apr_hash_t *left_props,
1025                            /*const*/ apr_hash_t *right_props,
1026                            const apr_array_header_t *prop_changes,
1027                            void *dir_baton,
1028                            const struct svn_diff_tree_processor_t *processor,
1029                            apr_pool_t *scratch_pool)
1030{
1031  struct copy_as_changed_baton_t *cb = processor->baton;
1032
1033  SVN_ERR(cb->processor->dir_changed(relpath,
1034                                     left_source,
1035                                     right_source,
1036                                     left_props,
1037                                     right_props,
1038                                     prop_changes,
1039                                     dir_baton,
1040                                     cb->processor,
1041                                     scratch_pool));
1042  return SVN_NO_ERROR;
1043}
1044
1045static svn_error_t *
1046copy_as_changed_dir_closed(const char *relpath,
1047                           const svn_diff_source_t *left_source,
1048                           const svn_diff_source_t *right_source,
1049                           void *dir_baton,
1050                           const svn_diff_tree_processor_t *processor,
1051                           apr_pool_t *scratch_pool)
1052{
1053  struct copy_as_changed_baton_t *cb = processor->baton;
1054
1055  SVN_ERR(cb->processor->dir_closed(relpath,
1056                                    left_source,
1057                                    right_source,
1058                                    dir_baton,
1059                                    cb->processor,
1060                                    scratch_pool));
1061  return SVN_NO_ERROR;
1062}
1063
1064static svn_error_t *
1065copy_as_changed_file_opened(void **new_file_baton,
1066                            svn_boolean_t *skip,
1067                            const char *relpath,
1068                            const svn_diff_source_t *left_source,
1069                            const svn_diff_source_t *right_source,
1070                            const svn_diff_source_t *copyfrom_source,
1071                            void *dir_baton,
1072                            const svn_diff_tree_processor_t *processor,
1073                            apr_pool_t *result_pool,
1074                            apr_pool_t *scratch_pool)
1075{
1076  struct copy_as_changed_baton_t *cb = processor->baton;
1077
1078  if (!left_source && copyfrom_source)
1079    {
1080      assert(right_source != NULL);
1081
1082      left_source = copyfrom_source;
1083      copyfrom_source = NULL;
1084    }
1085
1086  SVN_ERR(cb->processor->file_opened(new_file_baton,
1087                                     skip,
1088                                     relpath,
1089                                     left_source,
1090                                     right_source,
1091                                     copyfrom_source,
1092                                     dir_baton,
1093                                     cb->processor,
1094                                     result_pool,
1095                                     scratch_pool));
1096  return SVN_NO_ERROR;
1097}
1098
1099static svn_error_t *
1100copy_as_changed_file_added(const char *relpath,
1101                           const svn_diff_source_t *copyfrom_source,
1102                           const svn_diff_source_t *right_source,
1103                           const char *copyfrom_file,
1104                           const char *right_file,
1105                           /*const*/ apr_hash_t *copyfrom_props,
1106                           /*const*/ apr_hash_t *right_props,
1107                           void *file_baton,
1108                           const svn_diff_tree_processor_t *processor,
1109                           apr_pool_t *scratch_pool)
1110{
1111  struct copy_as_changed_baton_t *cb = processor->baton;
1112
1113  if (copyfrom_source)
1114    {
1115      apr_array_header_t *propchanges;
1116      svn_boolean_t same;
1117      SVN_ERR(svn_prop_diffs(&propchanges, right_props, copyfrom_props,
1118                             scratch_pool));
1119
1120      /* "" is sometimes a marker for just modified (E.g. no-textdeltas),
1121         and it is certainly not a file */
1122      if (*copyfrom_file && *right_file)
1123        {
1124          SVN_ERR(svn_io_files_contents_same_p(&same, copyfrom_file,
1125                                               right_file, scratch_pool));
1126        }
1127      else
1128        same = FALSE;
1129
1130      SVN_ERR(cb->processor->file_changed(relpath,
1131                                          copyfrom_source,
1132                                          right_source,
1133                                          copyfrom_file,
1134                                          right_file,
1135                                          copyfrom_props,
1136                                          right_props,
1137                                          !same,
1138                                          propchanges,
1139                                          file_baton,
1140                                          cb->processor,
1141                                          scratch_pool));
1142    }
1143  else
1144    {
1145      SVN_ERR(cb->processor->file_added(relpath,
1146                                        copyfrom_source,
1147                                        right_source,
1148                                        copyfrom_file,
1149                                        right_file,
1150                                        copyfrom_props,
1151                                        right_props,
1152                                        file_baton,
1153                                        cb->processor,
1154                                        scratch_pool));
1155    }
1156  return SVN_NO_ERROR;
1157}
1158
1159static svn_error_t *
1160copy_as_changed_file_deleted(const char *relpath,
1161                             const svn_diff_source_t *left_source,
1162                             const char *left_file,
1163                             /*const*/ apr_hash_t *left_props,
1164                             void *file_baton,
1165                             const svn_diff_tree_processor_t *processor,
1166                             apr_pool_t *scratch_pool)
1167{
1168  struct copy_as_changed_baton_t *cb = processor->baton;
1169
1170  SVN_ERR(cb->processor->file_deleted(relpath,
1171                                      left_source,
1172                                      left_file,
1173                                      left_props,
1174                                      file_baton,
1175                                      cb->processor,
1176                                      scratch_pool));
1177
1178  return SVN_NO_ERROR;
1179}
1180
1181static svn_error_t *
1182copy_as_changed_file_changed(const char *relpath,
1183                             const svn_diff_source_t *left_source,
1184                             const svn_diff_source_t *right_source,
1185                             const char *left_file,
1186                             const char *right_file,
1187                             /*const*/ apr_hash_t *left_props,
1188                             /*const*/ apr_hash_t *right_props,
1189                             svn_boolean_t file_modified,
1190                             const apr_array_header_t *prop_changes,
1191                             void *file_baton,
1192                             const svn_diff_tree_processor_t *processor,
1193                             apr_pool_t *scratch_pool)
1194{
1195  struct copy_as_changed_baton_t *cb = processor->baton;
1196
1197  SVN_ERR(cb->processor->file_changed(relpath,
1198                                      left_source,
1199                                      right_source,
1200                                      left_file,
1201                                      right_file,
1202                                      left_props,
1203                                      right_props,
1204                                      file_modified,
1205                                      prop_changes,
1206                                      file_baton,
1207                                      cb->processor,
1208                                      scratch_pool));
1209  return SVN_NO_ERROR;
1210}
1211
1212static svn_error_t *
1213copy_as_changed_file_closed(const char *relpath,
1214                            const svn_diff_source_t *left_source,
1215                            const svn_diff_source_t *right_source,
1216                            void *file_baton,
1217                            const svn_diff_tree_processor_t *processor,
1218                            apr_pool_t *scratch_pool)
1219{
1220  struct copy_as_changed_baton_t *cb = processor->baton;
1221
1222  SVN_ERR(cb->processor->file_closed(relpath,
1223                                     left_source,
1224                                     right_source,
1225                                     file_baton,
1226                                     cb->processor,
1227                                     scratch_pool));
1228
1229  return SVN_NO_ERROR;
1230}
1231
1232static svn_error_t *
1233copy_as_changed_node_absent(const char *relpath,
1234                            void *dir_baton,
1235                            const svn_diff_tree_processor_t *processor,
1236                            apr_pool_t *scratch_pool)
1237{
1238  struct copy_as_changed_baton_t *cb = processor->baton;
1239
1240  SVN_ERR(cb->processor->node_absent(relpath,
1241                                    dir_baton,
1242                                    cb->processor,
1243                                    scratch_pool));
1244  return SVN_NO_ERROR;
1245}
1246
1247
1248const svn_diff_tree_processor_t *
1249svn_diff__tree_processor_copy_as_changed_create(
1250                        const svn_diff_tree_processor_t * processor,
1251                        apr_pool_t *result_pool)
1252{
1253  struct copy_as_changed_baton_t *cb;
1254  svn_diff_tree_processor_t *filter;
1255
1256  cb = apr_pcalloc(result_pool, sizeof(*cb));
1257  cb->processor = processor;
1258
1259  filter = svn_diff__tree_processor_create(cb, result_pool);
1260  filter->dir_opened   = copy_as_changed_dir_opened;
1261  filter->dir_added    = copy_as_changed_dir_added;
1262  filter->dir_deleted  = copy_as_changed_dir_deleted;
1263  filter->dir_changed  = copy_as_changed_dir_changed;
1264  filter->dir_closed   = copy_as_changed_dir_closed;
1265
1266  filter->file_opened   = copy_as_changed_file_opened;
1267  filter->file_added    = copy_as_changed_file_added;
1268  filter->file_deleted  = copy_as_changed_file_deleted;
1269  filter->file_changed  = copy_as_changed_file_changed;
1270  filter->file_closed   = copy_as_changed_file_closed;
1271
1272  filter->node_absent   = copy_as_changed_node_absent;
1273
1274  return filter;
1275}
1276
1277
1278/* Processor baton for the tee tree processor */
1279struct tee_baton_t
1280{
1281  const svn_diff_tree_processor_t *p1;
1282  const svn_diff_tree_processor_t *p2;
1283};
1284
1285/* Wrapper baton for file and directory batons in the tee processor */
1286struct tee_node_baton_t
1287{
1288  void *baton1;
1289  void *baton2;
1290};
1291
1292static svn_error_t *
1293tee_dir_opened(void **new_dir_baton,
1294               svn_boolean_t *skip,
1295               svn_boolean_t *skip_children,
1296               const char *relpath,
1297               const svn_diff_source_t *left_source,
1298               const svn_diff_source_t *right_source,
1299               const svn_diff_source_t *copyfrom_source,
1300               void *parent_dir_baton,
1301               const svn_diff_tree_processor_t *processor,
1302               apr_pool_t *result_pool,
1303               apr_pool_t *scratch_pool)
1304{
1305  struct tee_baton_t *tb = processor->baton;
1306  struct tee_node_baton_t *pb = parent_dir_baton;
1307  struct tee_node_baton_t *nb = apr_pcalloc(result_pool, sizeof(*nb));
1308
1309  SVN_ERR(tb->p1->dir_opened(&(nb->baton1),
1310                             skip,
1311                             skip_children,
1312                             relpath,
1313                             left_source,
1314                             right_source,
1315                             copyfrom_source,
1316                             pb ? pb->baton1 : NULL,
1317                             tb->p1,
1318                             result_pool,
1319                             scratch_pool));
1320
1321  SVN_ERR(tb->p2->dir_opened(&(nb->baton2),
1322                             skip,
1323                             skip_children,
1324                             relpath,
1325                             left_source,
1326                             right_source,
1327                             copyfrom_source,
1328                             pb ? pb->baton2 : NULL,
1329                             tb->p2,
1330                             result_pool,
1331                             scratch_pool));
1332
1333  *new_dir_baton = nb;
1334
1335  return SVN_NO_ERROR;
1336}
1337
1338static svn_error_t *
1339tee_dir_added(const char *relpath,
1340              const svn_diff_source_t *copyfrom_source,
1341              const svn_diff_source_t *right_source,
1342              /*const*/ apr_hash_t *copyfrom_props,
1343              /*const*/ apr_hash_t *right_props,
1344              void *dir_baton,
1345              const svn_diff_tree_processor_t *processor,
1346              apr_pool_t *scratch_pool)
1347{
1348  struct tee_baton_t *tb = processor->baton;
1349  struct tee_node_baton_t *db = dir_baton;
1350
1351  SVN_ERR(tb->p1->dir_added(relpath,
1352                            copyfrom_source,
1353                            right_source,
1354                            copyfrom_props,
1355                            right_props,
1356                            db->baton1,
1357                            tb->p1,
1358                            scratch_pool));
1359
1360  SVN_ERR(tb->p2->dir_added(relpath,
1361                            copyfrom_source,
1362                            right_source,
1363                            copyfrom_props,
1364                            right_props,
1365                            db->baton2,
1366                            tb->p2,
1367                            scratch_pool));
1368
1369  return SVN_NO_ERROR;
1370}
1371
1372static svn_error_t *
1373tee_dir_deleted(const char *relpath,
1374                const svn_diff_source_t *left_source,
1375                /*const*/ apr_hash_t *left_props,
1376                void *dir_baton,
1377                const svn_diff_tree_processor_t *processor,
1378                apr_pool_t *scratch_pool)
1379{
1380  struct tee_baton_t *tb = processor->baton;
1381  struct tee_node_baton_t *db = dir_baton;
1382
1383  SVN_ERR(tb->p1->dir_deleted(relpath,
1384                              left_source,
1385                              left_props,
1386                              db->baton1,
1387                              tb->p1,
1388                              scratch_pool));
1389
1390  SVN_ERR(tb->p2->dir_deleted(relpath,
1391                              left_source,
1392                              left_props,
1393                              db->baton2,
1394                              tb->p2,
1395                              scratch_pool));
1396
1397  return SVN_NO_ERROR;
1398}
1399
1400static svn_error_t *
1401tee_dir_changed(const char *relpath,
1402                    const svn_diff_source_t *left_source,
1403                    const svn_diff_source_t *right_source,
1404                    /*const*/ apr_hash_t *left_props,
1405                    /*const*/ apr_hash_t *right_props,
1406                    const apr_array_header_t *prop_changes,
1407                    void *dir_baton,
1408                    const struct svn_diff_tree_processor_t *processor,
1409                    apr_pool_t *scratch_pool)
1410{
1411  struct tee_baton_t *tb = processor->baton;
1412  struct tee_node_baton_t *db = dir_baton;
1413
1414  SVN_ERR(tb->p1->dir_changed(relpath,
1415                              left_source,
1416                              right_source,
1417                              left_props,
1418                              right_props,
1419                              prop_changes,
1420                              db->baton1,
1421                              tb->p1,
1422                              scratch_pool));
1423
1424  SVN_ERR(tb->p2->dir_changed(relpath,
1425                              left_source,
1426                              right_source,
1427                              left_props,
1428                              right_props,
1429                              prop_changes,
1430                              db->baton2,
1431                              tb->p2,
1432                              scratch_pool));
1433  return SVN_NO_ERROR;
1434}
1435
1436static svn_error_t *
1437tee_dir_closed(const char *relpath,
1438               const svn_diff_source_t *left_source,
1439               const svn_diff_source_t *right_source,
1440               void *dir_baton,
1441               const svn_diff_tree_processor_t *processor,
1442               apr_pool_t *scratch_pool)
1443{
1444  struct tee_baton_t *tb = processor->baton;
1445  struct tee_node_baton_t *db = dir_baton;
1446
1447  SVN_ERR(tb->p1->dir_closed(relpath,
1448                             left_source,
1449                             right_source,
1450                             db->baton1,
1451                             tb->p1,
1452                             scratch_pool));
1453
1454  SVN_ERR(tb->p2->dir_closed(relpath,
1455                             left_source,
1456                             right_source,
1457                             db->baton2,
1458                             tb->p2,
1459                             scratch_pool));
1460  return SVN_NO_ERROR;
1461}
1462
1463static svn_error_t *
1464tee_file_opened(void **new_file_baton,
1465                svn_boolean_t *skip,
1466                const char *relpath,
1467                const svn_diff_source_t *left_source,
1468                const svn_diff_source_t *right_source,
1469                const svn_diff_source_t *copyfrom_source,
1470                void *dir_baton,
1471                const svn_diff_tree_processor_t *processor,
1472                apr_pool_t *result_pool,
1473                apr_pool_t *scratch_pool)
1474{
1475  struct tee_baton_t *tb = processor->baton;
1476  struct tee_node_baton_t *pb = dir_baton;
1477  struct tee_node_baton_t *nb = apr_pcalloc(result_pool, sizeof(*nb));
1478
1479  SVN_ERR(tb->p1->file_opened(&(nb->baton1),
1480                              skip,
1481                              relpath,
1482                              left_source,
1483                              right_source,
1484                              copyfrom_source,
1485                              pb ? pb->baton1 : NULL,
1486                              tb->p1,
1487                              result_pool,
1488                              scratch_pool));
1489
1490  SVN_ERR(tb->p2->file_opened(&(nb->baton2),
1491                              skip,
1492                              relpath,
1493                              left_source,
1494                              right_source,
1495                              copyfrom_source,
1496                              pb ? pb->baton2 : NULL,
1497                              tb->p2,
1498                              result_pool,
1499                              scratch_pool));
1500
1501  *new_file_baton = nb;
1502
1503  return SVN_NO_ERROR;
1504}
1505
1506static svn_error_t *
1507tee_file_added(const char *relpath,
1508               const svn_diff_source_t *copyfrom_source,
1509               const svn_diff_source_t *right_source,
1510               const char *copyfrom_file,
1511               const char *right_file,
1512               /*const*/ apr_hash_t *copyfrom_props,
1513               /*const*/ apr_hash_t *right_props,
1514               void *file_baton,
1515               const svn_diff_tree_processor_t *processor,
1516               apr_pool_t *scratch_pool)
1517{
1518  struct tee_baton_t *tb = processor->baton;
1519  struct tee_node_baton_t *fb = file_baton;
1520
1521  SVN_ERR(tb->p1->file_added(relpath,
1522                             copyfrom_source,
1523                             right_source,
1524                             copyfrom_file,
1525                             right_file,
1526                             copyfrom_props,
1527                             right_props,
1528                             fb->baton1,
1529                             tb->p1,
1530                             scratch_pool));
1531
1532  SVN_ERR(tb->p2->file_added(relpath,
1533                             copyfrom_source,
1534                             right_source,
1535                             copyfrom_file,
1536                             right_file,
1537                             copyfrom_props,
1538                             right_props,
1539                             fb->baton2,
1540                             tb->p2,
1541                             scratch_pool));
1542  return SVN_NO_ERROR;
1543}
1544
1545static svn_error_t *
1546tee_file_deleted(const char *relpath,
1547                 const svn_diff_source_t *left_source,
1548                 const char *left_file,
1549                 /*const*/ apr_hash_t *left_props,
1550                 void *file_baton,
1551                 const svn_diff_tree_processor_t *processor,
1552                 apr_pool_t *scratch_pool)
1553{
1554  struct tee_baton_t *tb = processor->baton;
1555  struct tee_node_baton_t *fb = file_baton;
1556
1557  SVN_ERR(tb->p1->file_deleted(relpath,
1558                               left_source,
1559                               left_file,
1560                               left_props,
1561                               fb->baton1,
1562                               tb->p1,
1563                               scratch_pool));
1564
1565  SVN_ERR(tb->p2->file_deleted(relpath,
1566                               left_source,
1567                               left_file,
1568                               left_props,
1569                               fb->baton2,
1570                               tb->p2,
1571                               scratch_pool));
1572  return SVN_NO_ERROR;
1573}
1574
1575static svn_error_t *
1576tee_file_changed(const char *relpath,
1577                 const svn_diff_source_t *left_source,
1578                 const svn_diff_source_t *right_source,
1579                 const char *left_file,
1580                 const char *right_file,
1581                 /*const*/ apr_hash_t *left_props,
1582                 /*const*/ apr_hash_t *right_props,
1583                 svn_boolean_t file_modified,
1584                 const apr_array_header_t *prop_changes,
1585                 void *file_baton,
1586                 const svn_diff_tree_processor_t *processor,
1587                 apr_pool_t *scratch_pool)
1588{
1589  struct tee_baton_t *tb = processor->baton;
1590  struct tee_node_baton_t *fb = file_baton;
1591
1592  SVN_ERR(tb->p1->file_changed(relpath,
1593                               left_source,
1594                               right_source,
1595                               left_file,
1596                               right_file,
1597                               left_props,
1598                               right_props,
1599                               file_modified,
1600                               prop_changes,
1601                               fb->baton1,
1602                               tb->p1,
1603                               scratch_pool));
1604
1605  SVN_ERR(tb->p2->file_changed(relpath,
1606                               left_source,
1607                               right_source,
1608                               left_file,
1609                               right_file,
1610                               left_props,
1611                               right_props,
1612                               file_modified,
1613                               prop_changes,
1614                               fb->baton2,
1615                               tb->p2,
1616                               scratch_pool));
1617  return SVN_NO_ERROR;
1618}
1619
1620static svn_error_t *
1621tee_file_closed(const char *relpath,
1622                    const svn_diff_source_t *left_source,
1623                    const svn_diff_source_t *right_source,
1624                    void *file_baton,
1625                    const svn_diff_tree_processor_t *processor,
1626                    apr_pool_t *scratch_pool)
1627{
1628  struct tee_baton_t *tb = processor->baton;
1629  struct tee_node_baton_t *fb = file_baton;
1630
1631  SVN_ERR(tb->p1->file_closed(relpath,
1632                              left_source,
1633                              right_source,
1634                              fb->baton1,
1635                              tb->p1,
1636                              scratch_pool));
1637
1638  SVN_ERR(tb->p2->file_closed(relpath,
1639                              left_source,
1640                              right_source,
1641                              fb->baton2,
1642                              tb->p2,
1643                              scratch_pool));
1644
1645  return SVN_NO_ERROR;
1646}
1647
1648static svn_error_t *
1649tee_node_absent(const char *relpath,
1650                void *dir_baton,
1651                const svn_diff_tree_processor_t *processor,
1652                apr_pool_t *scratch_pool)
1653{
1654  struct tee_baton_t *tb = processor->baton;
1655  struct tee_node_baton_t *db = dir_baton;
1656
1657  SVN_ERR(tb->p1->node_absent(relpath,
1658                              db ? db->baton1 : NULL,
1659                              tb->p1,
1660                              scratch_pool));
1661
1662  SVN_ERR(tb->p2->node_absent(relpath,
1663                              db ? db->baton2 : NULL,
1664                              tb->p2,
1665                              scratch_pool));
1666
1667  return SVN_NO_ERROR;
1668}
1669
1670const svn_diff_tree_processor_t *
1671svn_diff__tree_processor_tee_create(const svn_diff_tree_processor_t *processor1,
1672                                    const svn_diff_tree_processor_t *processor2,
1673                                    apr_pool_t *result_pool)
1674{
1675  struct tee_baton_t *tb = apr_pcalloc(result_pool, sizeof(*tb));
1676  svn_diff_tree_processor_t *tee;
1677  tb->p1 = processor1;
1678  tb->p2 = processor2;
1679
1680  tee = svn_diff__tree_processor_create(tb, result_pool);
1681
1682  tee->dir_opened    = tee_dir_opened;
1683  tee->dir_added     = tee_dir_added;
1684  tee->dir_deleted   = tee_dir_deleted;
1685  tee->dir_changed   = tee_dir_changed;
1686  tee->dir_closed    = tee_dir_closed;
1687  tee->file_opened   = tee_file_opened;
1688  tee->file_added    = tee_file_added;
1689  tee->file_deleted  = tee_file_deleted;
1690  tee->file_changed  = tee_file_changed;
1691  tee->file_closed   = tee_file_closed;
1692  tee->node_absent   = tee_node_absent;
1693
1694  return tee;
1695}
1696
1697svn_diff_source_t *
1698svn_diff__source_create(svn_revnum_t revision,
1699                        apr_pool_t *result_pool)
1700{
1701  svn_diff_source_t *src = apr_pcalloc(result_pool, sizeof(*src));
1702
1703  src->revision = revision;
1704  return src;
1705}
1706