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