2 * repos_diff.c -- The diff editor for comparing two repository versions
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
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. */
39 #include "svn_checksum.h"
42 #include "svn_pools.h"
43 #include "svn_dirent_uri.h"
46 #include "svn_props.h"
47 #include "svn_private_config.h"
51 #include "private/svn_subr_private.h"
52 #include "private/svn_wc_private.h"
53 #include "private/svn_editor.h"
54 #include "private/svn_sorts_private.h"
56 /* Overall crawler editor baton. */
58 /* The passed depth */
61 /* The result processor */
62 const svn_diff_tree_processor_t *processor;
64 /* RA_SESSION is the open session for making requests to the RA layer */
65 svn_ra_session_t *ra_session;
67 /* The rev1 from the '-r Rev1:Rev2' command line option */
68 svn_revnum_t revision;
70 /* The rev2 from the '-r Rev1:Rev2' option, specifically set by
71 set_target_revision(). */
72 svn_revnum_t target_revision;
74 /* The path to a temporary empty file used for add/delete
75 differences. The path is cached here so that it can be reused,
76 since all empty files are the same. */
77 const char *empty_file;
79 /* Empty hash used for adds. */
80 apr_hash_t *empty_hash;
82 /* Whether to report text deltas */
83 svn_boolean_t text_deltas;
85 /* A callback used to see if the client wishes to cancel the running
87 svn_cancel_func_t cancel_func;
89 /* A baton to pass to the cancellation callback. */
95 typedef struct deleted_path_notify_t
98 svn_wc_notify_action_t action;
99 svn_wc_notify_state_t state;
100 svn_boolean_t tree_conflicted;
101 } deleted_path_notify_t;
103 /* Directory level baton.
106 /* Gets set if the directory is added rather than replaced/unchanged. */
109 /* Gets set if this operation caused a tree-conflict on this directory
110 * (does not show tree-conflicts persisting from before this operation). */
111 svn_boolean_t tree_conflicted;
113 /* If TRUE, this node is skipped entirely.
114 * This is used to skip all children of a tree-conflicted
115 * directory without setting TREE_CONFLICTED to TRUE everywhere. */
118 /* If TRUE, all children of this directory are skipped. */
119 svn_boolean_t skip_children;
121 /* The path of the directory within the repository */
124 /* The baton for the parent directory, or null if this is the root of the
125 hierarchy to be compared. */
126 struct dir_baton *parent_baton;
128 /* The overall crawler editor baton. */
129 struct edit_baton *edit_baton;
131 /* A cache of any property changes (svn_prop_t) received for this dir. */
132 apr_array_header_t *propchanges;
134 /* Boolean indicating whether a node property was changed */
135 svn_boolean_t has_propchange;
137 /* Baton for svn_diff_tree_processor_t */
139 svn_diff_source_t *left_source;
140 svn_diff_source_t *right_source;
142 /* The pool passed in by add_dir, open_dir, or open_root.
143 Also, the pool this dir baton is allocated in. */
146 /* Base revision of directory. */
147 svn_revnum_t base_revision;
149 /* Number of users of baton. Its pool will be destroyed 0 */
156 /* Reference to parent baton */
157 struct dir_baton *parent_baton;
159 /* Gets set if the file is added rather than replaced. */
162 /* Gets set if this operation caused a tree-conflict on this file
163 * (does not show tree-conflicts persisting from before this operation). */
164 svn_boolean_t tree_conflicted;
166 /* If TRUE, this node is skipped entirely.
167 * This is currently used to skip all children of a tree-conflicted
171 /* The path of the file within the repository */
174 /* The path and APR file handle to the temporary file that contains the
175 first repository version. Also, the pristine-property list of
177 const char *path_start_revision;
178 apr_hash_t *pristine_props;
179 svn_revnum_t base_revision;
181 /* The path and APR file handle to the temporary file that contains the
182 second repository version. These fields are set when processing
183 textdelta and file deletion, and will be NULL if there's no
184 textual difference between the two revisions. */
185 const char *path_end_revision;
187 /* APPLY_HANDLER/APPLY_BATON represent the delta application baton. */
188 svn_txdelta_window_handler_t apply_handler;
191 /* The overall crawler editor baton. */
192 struct edit_baton *edit_baton;
194 /* Holds the checksum of the start revision file */
195 svn_checksum_t *start_md5_checksum;
197 /* Holds the resulting md5 digest of a textdelta transform */
198 unsigned char result_digest[APR_MD5_DIGESTSIZE];
199 svn_checksum_t *result_md5_checksum;
201 /* A cache of any property changes (svn_prop_t) received for this file. */
202 apr_array_header_t *propchanges;
204 /* Boolean indicating whether a node property was changed */
205 svn_boolean_t has_propchange;
207 /* Baton for svn_diff_tree_processor_t */
209 svn_diff_source_t *left_source;
210 svn_diff_source_t *right_source;
212 /* The pool passed in by add_file or open_file.
213 Also, the pool this file_baton is allocated in. */
217 /* Create a new directory baton for PATH in POOL. ADDED is set if
218 * this directory is being added rather than replaced. PARENT_BATON is
219 * the baton of the parent directory (or NULL if this is the root of
220 * the comparison hierarchy). The directory and its parent may or may
221 * not exist in the working copy. EDIT_BATON is the overall crawler
224 static struct dir_baton *
225 make_dir_baton(const char *path,
226 struct dir_baton *parent_baton,
227 struct edit_baton *edit_baton,
229 svn_revnum_t base_revision,
230 apr_pool_t *result_pool)
232 apr_pool_t *dir_pool = svn_pool_create(result_pool);
233 struct dir_baton *dir_baton = apr_pcalloc(dir_pool, sizeof(*dir_baton));
235 dir_baton->parent_baton = parent_baton;
236 dir_baton->edit_baton = edit_baton;
237 dir_baton->added = added;
238 dir_baton->tree_conflicted = FALSE;
239 dir_baton->skip = FALSE;
240 dir_baton->skip_children = FALSE;
241 dir_baton->pool = dir_pool;
242 dir_baton->path = apr_pstrdup(dir_pool, path);
243 dir_baton->propchanges = apr_array_make(dir_pool, 8, sizeof(svn_prop_t));
244 dir_baton->base_revision = base_revision;
248 parent_baton->users++;
253 /* New function. Called by everyone who has a reference when done */
255 release_dir(struct dir_baton *db)
257 assert(db->users > 0);
264 struct dir_baton *pb = db->parent_baton;
266 svn_pool_destroy(db->pool);
269 SVN_ERR(release_dir(pb));
275 /* Create a new file baton for PATH in POOL, which is a child of
276 * directory PARENT_PATH. ADDED is set if this file is being added
277 * rather than replaced. EDIT_BATON is a pointer to the global edit
280 static struct file_baton *
281 make_file_baton(const char *path,
282 struct dir_baton *parent_baton,
284 apr_pool_t *result_pool)
286 apr_pool_t *file_pool = svn_pool_create(result_pool);
287 struct file_baton *file_baton = apr_pcalloc(file_pool, sizeof(*file_baton));
289 file_baton->parent_baton = parent_baton;
290 file_baton->edit_baton = parent_baton->edit_baton;
291 file_baton->added = added;
292 file_baton->tree_conflicted = FALSE;
293 file_baton->skip = FALSE;
294 file_baton->pool = file_pool;
295 file_baton->path = apr_pstrdup(file_pool, path);
296 file_baton->propchanges = apr_array_make(file_pool, 8, sizeof(svn_prop_t));
297 file_baton->base_revision = parent_baton->edit_baton->revision;
299 parent_baton->users++;
304 /* Get revision FB->base_revision of the file described by FB from the
305 * repository, through FB->edit_baton->ra_session.
307 * Unless PROPS_ONLY is true:
308 * Set FB->path_start_revision to the path of a new temporary file containing
310 * Set FB->start_md5_checksum to that file's MD-5 checksum.
311 * Install a pool cleanup handler on FB->pool to delete the file.
314 * Set FB->pristine_props to a new hash containing the file's properties.
316 * Allocate all results in FB->pool.
319 get_file_from_ra(struct file_baton *fb,
320 svn_boolean_t props_only,
321 apr_pool_t *scratch_pool)
325 svn_stream_t *fstream;
327 SVN_ERR(svn_stream_open_unique(&fstream, &(fb->path_start_revision),
328 NULL, svn_io_file_del_on_pool_cleanup,
329 fb->pool, scratch_pool));
331 fstream = svn_stream_checksummed2(fstream, NULL, &fb->start_md5_checksum,
332 svn_checksum_md5, TRUE, fb->pool);
334 /* Retrieve the file and its properties */
335 SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session,
339 &(fb->pristine_props),
341 SVN_ERR(svn_stream_close(fstream));
345 SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session,
349 &(fb->pristine_props),
356 /* Remove every no-op property change from CHANGES: that is, remove every
357 entry in which the target value is the same as the value of the
358 corresponding property in PRISTINE_PROPS.
360 Issue #3657 'dav update report handler in skelta mode can cause
361 spurious conflicts'. When communicating with the repository via ra_serf,
362 the change_dir_prop and change_file_prop svn_delta_editor_t
363 callbacks are called (obviously) when a directory or file property has
364 changed between the start and end of the edit. Less obvious however,
365 is that these callbacks may be made describing *all* of the properties
366 on FILE_BATON->PATH when using the DAV providers, not just the change(s).
367 (Specifically ra_serf does it for diff/merge/update/switch).
369 This means that the change_[file|dir]_prop svn_delta_editor_t callbacks
370 may be made where there are no property changes (i.e. a noop change of
371 NAME from VALUE to VALUE). Normally this is harmless, but during a
372 merge it can result in spurious conflicts if the WC's pristine property
373 NAME has a value other than VALUE. In an ideal world the mod_dav_svn
374 update report handler, when in 'skelta' mode and describing changes to
375 a path on which a property has changed, wouldn't ask the client to later
376 fetch all properties and figure out what has changed itself. The server
377 already knows which properties have changed!
379 Regardless, such a change is not yet implemented, and even when it is,
380 the client should DTRT with regard to older servers which behave this
381 way. Hence this little hack: We populate FILE_BATON->PROPCHANGES only
382 with *actual* property changes.
384 See https://issues.apache.org/jira/browse/SVN-3657#desc9 and
385 http://svn.haxx.se/dev/archive-2010-08/0351.shtml for more details.
388 remove_non_prop_changes(apr_hash_t *pristine_props,
389 apr_array_header_t *changes)
393 /* For added nodes, there is nothing to filter. */
394 if (apr_hash_count(pristine_props) == 0)
397 for (i = 0; i < changes->nelts; i++)
399 svn_prop_t *change = &APR_ARRAY_IDX(changes, i, svn_prop_t);
403 const svn_string_t *old_val = svn_hash_gets(pristine_props,
406 if (old_val && svn_string_compare(old_val, change->value))
408 /* Remove the matching change and re-check the current index */
409 SVN_ERR(svn_sort__array_delete2(changes, i, 1));
417 /* Get the empty file associated with the edit baton. This is cached so
418 * that it can be reused, all empty files are the same.
421 get_empty_file(struct edit_baton *eb,
422 const char **empty_file_path)
424 /* Create the file if it does not exist */
425 /* Note that we tried to use /dev/null in r857294, but
426 that won't work on Windows: it's impossible to stat NUL */
428 SVN_ERR(svn_io_open_unique_file3(NULL, &(eb->empty_file), NULL,
429 svn_io_file_del_on_pool_cleanup,
430 eb->pool, eb->pool));
432 *empty_file_path = eb->empty_file;
437 /* An svn_delta_editor_t function. */
439 set_target_revision(void *edit_baton,
440 svn_revnum_t target_revision,
443 struct edit_baton *eb = edit_baton;
445 eb->target_revision = target_revision;
449 /* An svn_delta_editor_t function. The root of the comparison hierarchy */
451 open_root(void *edit_baton,
452 svn_revnum_t base_revision,
456 struct edit_baton *eb = edit_baton;
457 struct dir_baton *db = make_dir_baton("", NULL, eb, FALSE, base_revision,
460 db->left_source = svn_diff__source_create(eb->revision, db->pool);
461 db->right_source = svn_diff__source_create(eb->target_revision, db->pool);
463 SVN_ERR(eb->processor->dir_opened(&db->pdb,
473 db->pool /* scratch_pool */));
479 /* Compare a file being deleted against an empty file.
482 diff_deleted_file(const char *path,
483 struct dir_baton *db,
484 apr_pool_t *scratch_pool)
486 struct edit_baton *eb = db->edit_baton;
487 struct file_baton *fb = make_file_baton(path, db, FALSE, scratch_pool);
488 svn_boolean_t skip = FALSE;
489 svn_diff_source_t *left_source = svn_diff__source_create(eb->revision,
493 SVN_ERR(eb->cancel_func(eb->cancel_baton));
495 SVN_ERR(eb->processor->file_opened(&fb->pfb, &skip, path,
497 NULL /* right_source */,
498 NULL /* copyfrom_source */,
501 scratch_pool, scratch_pool));
504 SVN_ERR(eb->cancel_func(eb->cancel_baton));
509 SVN_ERR(get_file_from_ra(fb, ! eb->text_deltas, scratch_pool));
511 SVN_ERR(eb->processor->file_deleted(fb->path,
513 fb->path_start_revision,
522 /* Recursively walk tree rooted at DIR (at EB->revision) in the repository,
523 * reporting all children as deleted. Part of a workaround for issue 2333.
525 * DIR is a repository path relative to the URL in EB->ra_session. EB is
526 * the overall crawler editor baton. EB->revision must be a valid revision
527 * number, not SVN_INVALID_REVNUM. Use EB->cancel_func (if not null) with
528 * EB->cancel_baton for cancellation.
530 /* ### TODO: Handle depth. */
532 diff_deleted_dir(const char *path,
533 struct dir_baton *pb,
534 apr_pool_t *scratch_pool)
536 struct edit_baton *eb = pb->edit_baton;
537 struct dir_baton *db;
538 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
539 svn_boolean_t skip = FALSE;
540 svn_boolean_t skip_children = FALSE;
541 apr_hash_t *dirents = NULL;
542 apr_hash_t *left_props = NULL;
543 svn_diff_source_t *left_source = svn_diff__source_create(eb->revision,
545 db = make_dir_baton(path, pb, pb->edit_baton, FALSE, SVN_INVALID_REVNUM,
548 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(eb->revision));
551 SVN_ERR(eb->cancel_func(eb->cancel_baton));
553 SVN_ERR(eb->processor->dir_opened(&db->pdb, &skip, &skip_children,
556 NULL /* right_source */,
557 NULL /* copyfrom_source */,
560 scratch_pool, iterpool));
562 if (!skip || !skip_children)
563 SVN_ERR(svn_ra_get_dir2(eb->ra_session,
564 skip_children ? NULL : &dirents,
566 skip ? NULL : &left_props,
572 /* The "old" dir will be skipped by the repository report. If required,
573 * crawl it recursively, diffing each file against the empty file. This
574 * is a workaround for issue 2333 "'svn diff URL1 URL2' not reverse of
575 * 'svn diff URL2 URL1'". */
578 apr_hash_index_t *hi;
580 for (hi = apr_hash_first(scratch_pool, dirents); hi;
581 hi = apr_hash_next(hi))
583 const char *child_path;
584 const char *name = apr_hash_this_key(hi);
585 svn_dirent_t *dirent = apr_hash_this_val(hi);
587 svn_pool_clear(iterpool);
589 child_path = svn_relpath_join(path, name, iterpool);
591 if (dirent->kind == svn_node_file)
593 SVN_ERR(diff_deleted_file(child_path, db, iterpool));
595 else if (dirent->kind == svn_node_dir)
597 SVN_ERR(diff_deleted_dir(child_path, db, iterpool));
604 SVN_ERR(eb->processor->dir_deleted(path,
612 SVN_ERR(release_dir(db));
614 svn_pool_destroy(iterpool);
618 /* An svn_delta_editor_t function. */
620 delete_entry(const char *path,
621 svn_revnum_t base_revision,
625 struct dir_baton *pb = parent_baton;
626 struct edit_baton *eb = pb->edit_baton;
627 svn_node_kind_t kind;
628 apr_pool_t *scratch_pool;
631 if (pb->skip_children)
634 scratch_pool = svn_pool_create(eb->pool);
636 /* We need to know if this is a directory or a file */
637 SVN_ERR(svn_ra_check_path(eb->ra_session, path, eb->revision, &kind,
644 SVN_ERR(diff_deleted_file(path, pb, scratch_pool));
649 SVN_ERR(diff_deleted_dir(path, pb, scratch_pool));
656 svn_pool_destroy(scratch_pool);
661 /* An svn_delta_editor_t function. */
663 add_directory(const char *path,
665 const char *copyfrom_path,
666 svn_revnum_t copyfrom_revision,
670 struct dir_baton *pb = parent_baton;
671 struct edit_baton *eb = pb->edit_baton;
672 struct dir_baton *db;
674 /* ### TODO: support copyfrom? */
676 db = make_dir_baton(path, pb, eb, TRUE, SVN_INVALID_REVNUM, pb->pool);
679 /* Skip *everything* within a newly tree-conflicted directory,
680 * and directories the children of which should be skipped. */
681 if (pb->skip_children)
684 db->skip_children = TRUE;
688 db->right_source = svn_diff__source_create(eb->target_revision,
691 SVN_ERR(eb->processor->dir_opened(&db->pdb,
697 NULL /* copyfrom_source */,
700 db->pool, db->pool));
705 /* An svn_delta_editor_t function. */
707 open_directory(const char *path,
709 svn_revnum_t base_revision,
713 struct dir_baton *pb = parent_baton;
714 struct edit_baton *eb = pb->edit_baton;
715 struct dir_baton *db;
717 db = make_dir_baton(path, pb, eb, FALSE, base_revision, pb->pool);
722 if (pb->skip_children)
725 db->skip_children = TRUE;
729 db->left_source = svn_diff__source_create(eb->revision, db->pool);
730 db->right_source = svn_diff__source_create(eb->target_revision, db->pool);
732 SVN_ERR(eb->processor->dir_opened(&db->pdb,
733 &db->skip, &db->skip_children,
740 db->pool, db->pool));
746 /* An svn_delta_editor_t function. */
748 add_file(const char *path,
750 const char *copyfrom_path,
751 svn_revnum_t copyfrom_revision,
755 struct dir_baton *pb = parent_baton;
756 struct edit_baton *eb = pb->edit_baton;
757 struct file_baton *fb;
759 /* ### TODO: support copyfrom? */
761 fb = make_file_baton(path, pb, TRUE, pb->pool);
765 if (pb->skip_children)
771 fb->pristine_props = pb->edit_baton->empty_hash;
773 fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool);
775 SVN_ERR(eb->processor->file_opened(&fb->pfb,
780 NULL /* copy source */,
783 fb->pool, fb->pool));
788 /* An svn_delta_editor_t function. */
790 open_file(const char *path,
792 svn_revnum_t base_revision,
796 struct dir_baton *pb = parent_baton;
797 struct file_baton *fb;
798 struct edit_baton *eb = pb->edit_baton;
799 fb = make_file_baton(path, pb, FALSE, pb->pool);
803 if (pb->skip_children)
809 fb->base_revision = base_revision;
811 fb->left_source = svn_diff__source_create(eb->revision, fb->pool);
812 fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool);
814 SVN_ERR(eb->processor->file_opened(&fb->pfb,
819 NULL /* copy source */,
822 fb->pool, fb->pool));
827 /* Do the work of applying the text delta. */
829 window_handler(svn_txdelta_window_t *window,
832 struct file_baton *fb = window_baton;
834 SVN_ERR(fb->apply_handler(window, fb->apply_baton));
838 fb->result_md5_checksum = svn_checksum__from_digest_md5(
846 /* Implements svn_stream_lazyopen_func_t. */
848 lazy_open_source(svn_stream_t **stream,
850 apr_pool_t *result_pool,
851 apr_pool_t *scratch_pool)
853 struct file_baton *fb = baton;
855 SVN_ERR(svn_stream_open_readonly(stream, fb->path_start_revision,
856 result_pool, scratch_pool));
861 /* Implements svn_stream_lazyopen_func_t. */
863 lazy_open_result(svn_stream_t **stream,
865 apr_pool_t *result_pool,
866 apr_pool_t *scratch_pool)
868 struct file_baton *fb = baton;
870 SVN_ERR(svn_stream_open_unique(stream, &fb->path_end_revision, NULL,
871 svn_io_file_del_on_pool_cleanup,
872 result_pool, scratch_pool));
877 /* An svn_delta_editor_t function. */
879 apply_textdelta(void *file_baton,
880 const char *base_md5_digest,
882 svn_txdelta_window_handler_t *handler,
883 void **handler_baton)
885 struct file_baton *fb = file_baton;
886 svn_stream_t *src_stream;
887 svn_stream_t *result_stream;
888 apr_pool_t *scratch_pool = fb->pool;
890 /* Skip *everything* within a newly tree-conflicted directory. */
893 *handler = svn_delta_noop_window_handler;
894 *handler_baton = NULL;
898 /* If we're not sending file text, then ignore any that we receive. */
899 if (! fb->edit_baton->text_deltas)
901 /* Supply valid paths to indicate there is a text change. */
902 SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_start_revision));
903 SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_end_revision));
905 *handler = svn_delta_noop_window_handler;
906 *handler_baton = NULL;
911 /* We need the expected pristine file, so go get it */
913 SVN_ERR(get_file_from_ra(fb, FALSE, scratch_pool));
915 SVN_ERR(get_empty_file(fb->edit_baton, &(fb->path_start_revision)));
917 SVN_ERR_ASSERT(fb->path_start_revision != NULL);
919 if (base_md5_digest != NULL)
921 svn_checksum_t *base_md5_checksum;
923 SVN_ERR(svn_checksum_parse_hex(&base_md5_checksum, svn_checksum_md5,
924 base_md5_digest, scratch_pool));
926 if (!svn_checksum_match(base_md5_checksum, fb->start_md5_checksum))
927 return svn_error_trace(svn_checksum_mismatch_err(
929 fb->start_md5_checksum,
931 _("Base checksum mismatch for '%s'"),
935 /* Open the file to be used as the base for second revision */
936 src_stream = svn_stream_lazyopen_create(lazy_open_source, fb, TRUE,
939 /* Open the file that will become the second revision after applying the
940 text delta, it starts empty */
941 result_stream = svn_stream_lazyopen_create(lazy_open_result, fb, TRUE,
944 svn_txdelta_apply(src_stream,
948 &(fb->apply_handler), &(fb->apply_baton));
950 *handler = window_handler;
951 *handler_baton = file_baton;
956 /* An svn_delta_editor_t function. When the file is closed we have a temporary
957 * file containing a pristine version of the repository file. This can
958 * be compared against the working copy.
960 * ### Ignore TEXT_CHECKSUM for now. Someday we can use it to verify
961 * ### the integrity of the file being diffed. Done efficiently, this
962 * ### would probably involve calculating the checksum as the data is
963 * ### received, storing the final checksum in the file_baton, and
964 * ### comparing against it here.
967 close_file(void *file_baton,
968 const char *expected_md5_digest,
971 struct file_baton *fb = file_baton;
972 struct dir_baton *pb = fb->parent_baton;
973 struct edit_baton *eb = fb->edit_baton;
974 apr_pool_t *scratch_pool;
976 /* Skip *everything* within a newly tree-conflicted directory. */
979 svn_pool_destroy(fb->pool);
980 SVN_ERR(release_dir(pb));
984 scratch_pool = fb->pool;
986 if (expected_md5_digest && eb->text_deltas)
988 svn_checksum_t *expected_md5_checksum;
990 SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
991 expected_md5_digest, scratch_pool));
993 if (!svn_checksum_match(expected_md5_checksum, fb->result_md5_checksum))
994 return svn_error_trace(svn_checksum_mismatch_err(
995 expected_md5_checksum,
996 fb->result_md5_checksum,
998 _("Checksum mismatch for '%s'"),
1002 if (fb->added || fb->path_end_revision || fb->has_propchange)
1004 apr_hash_t *right_props;
1006 if (!fb->added && !fb->pristine_props)
1008 /* We didn't receive a text change, so we have no pristine props.
1009 Retrieve just the props now. */
1010 SVN_ERR(get_file_from_ra(fb, TRUE, scratch_pool));
1013 if (fb->pristine_props)
1014 SVN_ERR(remove_non_prop_changes(fb->pristine_props, fb->propchanges));
1016 right_props = svn_prop__patch(fb->pristine_props, fb->propchanges,
1020 SVN_ERR(eb->processor->file_added(fb->path,
1021 NULL /* copyfrom_src */,
1023 NULL /* copyfrom_file */,
1024 fb->path_end_revision,
1025 NULL /* copyfrom_props */,
1031 SVN_ERR(eb->processor->file_changed(fb->path,
1034 fb->path_end_revision
1035 ? fb->path_start_revision
1037 fb->path_end_revision,
1040 (fb->path_end_revision != NULL),
1047 svn_pool_destroy(fb->pool); /* Destroy file and scratch pool */
1049 SVN_ERR(release_dir(pb));
1051 return SVN_NO_ERROR;
1054 /* Report any accumulated prop changes via the 'dir_props_changed' callback,
1055 * and then call the 'dir_closed' callback. Notify about any deleted paths
1056 * within this directory that have not already been notified, and then about
1057 * this directory itself (unless it was added, in which case the notification
1058 * was done at that time).
1060 * An svn_delta_editor_t function. */
1061 static svn_error_t *
1062 close_directory(void *dir_baton,
1065 struct dir_baton *db = dir_baton;
1066 struct edit_baton *eb = db->edit_baton;
1067 apr_pool_t *scratch_pool;
1068 apr_hash_t *pristine_props;
1069 svn_boolean_t send_changed = FALSE;
1071 scratch_pool = db->pool;
1073 if ((db->has_propchange || db->added) && !db->skip)
1077 pristine_props = eb->empty_hash;
1081 SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, &pristine_props,
1082 db->path, db->base_revision, 0, scratch_pool));
1085 if (db->propchanges->nelts > 0)
1087 SVN_ERR(remove_non_prop_changes(pristine_props, db->propchanges));
1090 if (db->propchanges->nelts > 0 || db->added)
1092 apr_hash_t *right_props;
1094 right_props = svn_prop__patch(pristine_props, db->propchanges,
1099 SVN_ERR(eb->processor->dir_added(db->path,
1100 NULL /* copyfrom */,
1102 NULL /* copyfrom props */,
1110 SVN_ERR(eb->processor->dir_changed(db->path,
1121 send_changed = TRUE; /* Skip dir_closed */
1125 if (! db->skip && !send_changed)
1127 SVN_ERR(eb->processor->dir_closed(db->path,
1134 SVN_ERR(release_dir(db));
1136 return SVN_NO_ERROR;
1140 /* Record a prop change, which we will report later in close_file().
1142 * An svn_delta_editor_t function. */
1143 static svn_error_t *
1144 change_file_prop(void *file_baton,
1146 const svn_string_t *value,
1149 struct file_baton *fb = file_baton;
1150 svn_prop_t *propchange;
1151 svn_prop_kind_t propkind;
1153 /* Skip *everything* within a newly tree-conflicted directory. */
1155 return SVN_NO_ERROR;
1157 propkind = svn_property_kind2(name);
1158 if (propkind == svn_prop_wc_kind)
1159 return SVN_NO_ERROR;
1160 else if (propkind == svn_prop_regular_kind)
1161 fb->has_propchange = TRUE;
1163 propchange = apr_array_push(fb->propchanges);
1164 propchange->name = apr_pstrdup(fb->pool, name);
1165 propchange->value = svn_string_dup(value, fb->pool);
1167 return SVN_NO_ERROR;
1170 /* Make a note of this prop change, to be reported when the dir is closed.
1172 * An svn_delta_editor_t function. */
1173 static svn_error_t *
1174 change_dir_prop(void *dir_baton,
1176 const svn_string_t *value,
1179 struct dir_baton *db = dir_baton;
1180 svn_prop_t *propchange;
1181 svn_prop_kind_t propkind;
1183 /* Skip *everything* within a newly tree-conflicted directory. */
1185 return SVN_NO_ERROR;
1187 propkind = svn_property_kind2(name);
1188 if (propkind == svn_prop_wc_kind)
1189 return SVN_NO_ERROR;
1190 else if (propkind == svn_prop_regular_kind)
1191 db->has_propchange = TRUE;
1193 propchange = apr_array_push(db->propchanges);
1194 propchange->name = apr_pstrdup(db->pool, name);
1195 propchange->value = svn_string_dup(value, db->pool);
1197 return SVN_NO_ERROR;
1201 /* An svn_delta_editor_t function. */
1202 static svn_error_t *
1203 close_edit(void *edit_baton,
1206 struct edit_baton *eb = edit_baton;
1208 svn_pool_destroy(eb->pool);
1210 return SVN_NO_ERROR;
1213 /* Notify that the node at PATH is 'missing'.
1214 * An svn_delta_editor_t function. */
1215 static svn_error_t *
1216 absent_directory(const char *path,
1220 struct dir_baton *pb = parent_baton;
1221 struct edit_baton *eb = pb->edit_baton;
1223 SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool));
1225 return SVN_NO_ERROR;
1229 /* Notify that the node at PATH is 'missing'.
1230 * An svn_delta_editor_t function. */
1231 static svn_error_t *
1232 absent_file(const char *path,
1236 struct dir_baton *pb = parent_baton;
1237 struct edit_baton *eb = pb->edit_baton;
1239 SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool));
1241 return SVN_NO_ERROR;
1244 static svn_error_t *
1245 fetch_kind_func(svn_node_kind_t *kind,
1248 svn_revnum_t base_revision,
1249 apr_pool_t *scratch_pool)
1251 struct edit_baton *eb = baton;
1253 if (!SVN_IS_VALID_REVNUM(base_revision))
1254 base_revision = eb->revision;
1256 SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind,
1259 return SVN_NO_ERROR;
1262 static svn_error_t *
1263 fetch_props_func(apr_hash_t **props,
1266 svn_revnum_t base_revision,
1267 apr_pool_t *result_pool,
1268 apr_pool_t *scratch_pool)
1270 struct edit_baton *eb = baton;
1271 svn_node_kind_t node_kind;
1273 if (!SVN_IS_VALID_REVNUM(base_revision))
1274 base_revision = eb->revision;
1276 SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind,
1279 if (node_kind == svn_node_file)
1281 SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision,
1282 NULL, NULL, props, result_pool));
1284 else if (node_kind == svn_node_dir)
1286 apr_array_header_t *tmp_props;
1288 SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path,
1289 base_revision, 0 /* Dirent fields */,
1291 tmp_props = svn_prop_hash_to_array(*props, result_pool);
1292 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1294 *props = svn_prop_array_to_hash(tmp_props, result_pool);
1298 *props = apr_hash_make(result_pool);
1301 return SVN_NO_ERROR;
1304 static svn_error_t *
1305 fetch_base_func(const char **filename,
1308 svn_revnum_t base_revision,
1309 apr_pool_t *result_pool,
1310 apr_pool_t *scratch_pool)
1312 struct edit_baton *eb = baton;
1313 svn_stream_t *fstream;
1316 if (!SVN_IS_VALID_REVNUM(base_revision))
1317 base_revision = eb->revision;
1319 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
1320 svn_io_file_del_on_pool_cleanup,
1321 result_pool, scratch_pool));
1323 err = svn_ra_get_file(eb->ra_session, path, base_revision,
1324 fstream, NULL, NULL, scratch_pool);
1325 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1327 svn_error_clear(err);
1328 SVN_ERR(svn_stream_close(fstream));
1331 return SVN_NO_ERROR;
1334 return svn_error_trace(err);
1336 SVN_ERR(svn_stream_close(fstream));
1338 return SVN_NO_ERROR;
1341 /* Create a repository diff editor and baton. */
1343 svn_client__get_diff_editor2(const svn_delta_editor_t **editor,
1345 svn_ra_session_t *ra_session,
1347 svn_revnum_t revision,
1348 svn_boolean_t text_deltas,
1349 const svn_diff_tree_processor_t *processor,
1350 svn_cancel_func_t cancel_func,
1352 apr_pool_t *result_pool)
1354 apr_pool_t *editor_pool = svn_pool_create(result_pool);
1355 svn_delta_editor_t *tree_editor = svn_delta_default_editor(editor_pool);
1356 struct edit_baton *eb = apr_pcalloc(editor_pool, sizeof(*eb));
1357 svn_delta_shim_callbacks_t *shim_callbacks =
1358 svn_delta_shim_callbacks_default(editor_pool);
1360 eb->pool = editor_pool;
1363 eb->processor = processor;
1365 eb->ra_session = ra_session;
1367 eb->revision = revision;
1368 eb->target_revision = SVN_INVALID_REVNUM;
1369 eb->empty_file = NULL;
1370 eb->empty_hash = apr_hash_make(eb->pool);
1371 eb->text_deltas = text_deltas;
1372 eb->cancel_func = cancel_func;
1373 eb->cancel_baton = cancel_baton;
1375 tree_editor->set_target_revision = set_target_revision;
1376 tree_editor->open_root = open_root;
1377 tree_editor->delete_entry = delete_entry;
1378 tree_editor->add_directory = add_directory;
1379 tree_editor->open_directory = open_directory;
1380 tree_editor->add_file = add_file;
1381 tree_editor->open_file = open_file;
1382 tree_editor->apply_textdelta = apply_textdelta;
1383 tree_editor->close_file = close_file;
1384 tree_editor->close_directory = close_directory;
1385 tree_editor->change_file_prop = change_file_prop;
1386 tree_editor->change_dir_prop = change_dir_prop;
1387 tree_editor->close_edit = close_edit;
1388 tree_editor->absent_directory = absent_directory;
1389 tree_editor->absent_file = absent_file;
1391 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1396 shim_callbacks->fetch_kind_func = fetch_kind_func;
1397 shim_callbacks->fetch_props_func = fetch_props_func;
1398 shim_callbacks->fetch_base_func = fetch_base_func;
1399 shim_callbacks->fetch_baton = eb;
1401 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1402 NULL, NULL, shim_callbacks,
1403 result_pool, result_pool));
1405 return SVN_NO_ERROR;