2 * adm_crawler.c: report local WC mods to an Editor.
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 /* ==================================================================== */
29 #include <apr_pools.h>
30 #include <apr_file_io.h>
34 #include "svn_types.h"
35 #include "svn_pools.h"
38 #include "svn_delta.h"
39 #include "svn_dirent_uri.h"
42 #include "private/svn_wc_private.h"
45 #include "adm_files.h"
46 #include "translate.h"
47 #include "workqueue.h"
48 #include "conflicts.h"
50 #include "svn_private_config.h"
53 /* Helper for report_revisions_and_depths().
55 Perform an atomic restoration of the file LOCAL_ABSPATH; that is, copy
56 the file's text-base to the administrative tmp area, and then move
57 that file to LOCAL_ABSPATH with possible translations/expansions. If
58 USE_COMMIT_TIMES is set, then set working file's timestamp to
59 last-commit-time. Either way, set entry-timestamp to match that of
60 the working file when all is finished.
62 If MARK_RESOLVED_TEXT_CONFLICT is TRUE, mark as resolved any existing
63 text conflict on LOCAL_ABSPATH.
65 Not that a valid access baton with a write lock to the directory of
66 LOCAL_ABSPATH must be available in DB.*/
68 restore_file(svn_wc__db_t *db,
69 const char *local_abspath,
70 svn_boolean_t use_commit_times,
71 svn_boolean_t mark_resolved_text_conflict,
72 svn_cancel_func_t cancel_func,
74 apr_pool_t *scratch_pool)
76 svn_skel_t *work_item;
78 SVN_ERR(svn_wc__wq_build_file_install(&work_item,
80 NULL /* source_abspath */,
82 TRUE /* record_fileinfo */,
83 scratch_pool, scratch_pool));
84 /* ### we need an existing path for wq_add. not entirely WRI_ABSPATH yet */
85 SVN_ERR(svn_wc__db_wq_add(db,
86 svn_dirent_dirname(local_abspath, scratch_pool),
87 work_item, scratch_pool));
89 /* Run the work item immediately. */
90 SVN_ERR(svn_wc__wq_run(db, local_abspath,
91 cancel_func, cancel_baton,
94 /* Remove any text conflict */
95 if (mark_resolved_text_conflict)
96 SVN_ERR(svn_wc__mark_resolved_text_conflict(db, local_abspath,
97 cancel_func, cancel_baton,
104 svn_wc_restore(svn_wc_context_t *wc_ctx,
105 const char *local_abspath,
106 svn_boolean_t use_commit_times,
107 apr_pool_t *scratch_pool)
109 /* ### If ever revved: Add cancel func. */
110 svn_wc__db_status_t status;
111 svn_node_kind_t kind;
112 svn_node_kind_t disk_kind;
113 const svn_checksum_t *checksum;
115 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
117 if (disk_kind != svn_node_none)
118 return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
119 _("The existing node '%s' can not be restored."),
120 svn_dirent_local_style(local_abspath,
123 SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
124 NULL, NULL, NULL, &checksum, NULL, NULL, NULL, NULL,
125 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
126 NULL, NULL, NULL, NULL,
127 wc_ctx->db, local_abspath,
128 scratch_pool, scratch_pool));
130 if (status != svn_wc__db_status_normal
131 && !((status == svn_wc__db_status_added
132 || status == svn_wc__db_status_incomplete)
133 && (kind == svn_node_dir
134 || (kind == svn_node_file && checksum != NULL)
135 /* || (kind == svn_node_symlink && target)*/)))
137 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
138 _("The node '%s' can not be restored."),
139 svn_dirent_local_style(local_abspath,
143 if (kind == svn_node_file || kind == svn_node_symlink)
144 SVN_ERR(restore_file(wc_ctx->db, local_abspath, use_commit_times,
145 FALSE /*mark_resolved_text_conflict*/,
146 NULL, NULL /* cancel func, baton */,
149 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
154 /* Try to restore LOCAL_ABSPATH of node type KIND and if successful,
155 notify that the node is restored. Use DB for accessing the working copy.
156 If USE_COMMIT_TIMES is set, then set working file's timestamp to
159 This function does all temporary allocations in SCRATCH_POOL
162 restore_node(svn_wc__db_t *db,
163 const char *local_abspath,
164 svn_node_kind_t kind,
165 svn_boolean_t mark_resolved_text_conflict,
166 svn_boolean_t use_commit_times,
167 svn_cancel_func_t cancel_func,
169 svn_wc_notify_func2_t notify_func,
171 apr_pool_t *scratch_pool)
173 if (kind == svn_node_file || kind == svn_node_symlink)
175 /* Recreate file from text-base; mark any text conflict as resolved */
176 SVN_ERR(restore_file(db, local_abspath, use_commit_times,
177 mark_resolved_text_conflict,
178 cancel_func, cancel_baton,
181 else if (kind == svn_node_dir)
183 /* Recreating a directory is just a mkdir */
184 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
187 /* ... report the restoration to the caller. */
188 if (notify_func != NULL)
190 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
191 svn_wc_notify_restore,
193 notify->kind = svn_node_file;
194 (*notify_func)(notify_baton, notify, scratch_pool);
200 /* The recursive crawler that describes a mixed-revision working
201 copy to an RA layer. Used to initiate updates.
203 This is a depth-first recursive walk of the children of DIR_ABSPATH
204 (not including DIR_ABSPATH itself) using DB. Look at each node and
205 check if its revision is different than DIR_REV. If so, report this
206 fact to REPORTER. If a node has a different URL than expected, or
207 a different depth than its parent, report that to REPORTER.
209 Report DIR_ABSPATH to the reporter as REPORT_RELPATH.
211 Alternatively, if REPORT_EVERYTHING is set, then report all
212 children unconditionally.
214 DEPTH is actually the *requested* depth for the update-like
215 operation for which we are reporting working copy state. However,
216 certain requested depths affect the depth of the report crawl. For
217 example, if the requested depth is svn_depth_empty, there's no
218 point descending into subdirs, no matter what their depths. So:
220 If DEPTH is svn_depth_empty, don't report any files and don't
221 descend into any subdirs. If svn_depth_files, report files but
222 still don't descend into subdirs. If svn_depth_immediates, report
223 files, and report subdirs themselves but not their entries. If
224 svn_depth_infinity or svn_depth_unknown, report everything all the
225 way down. (That last sentence might sound counterintuitive, but
226 since you can't go deeper than the local ambient depth anyway,
227 requesting svn_depth_infinity really means "as deep as the various
228 parts of this working copy go". Of course, the information that
229 comes back from the server will be different for svn_depth_unknown
230 than for svn_depth_infinity.)
232 DIR_REPOS_RELPATH, DIR_REPOS_ROOT and DIR_DEPTH are the repository
233 relative path, the repository root and depth stored on the directory,
234 passed here to avoid another database query.
236 DEPTH_COMPATIBILITY_TRICK means the same thing here as it does
237 in svn_wc_crawl_revisions5().
239 If RESTORE_FILES is set, then unexpectedly missing working files
240 will be restored from text-base and NOTIFY_FUNC/NOTIFY_BATON
241 will be called to report the restoration. USE_COMMIT_TIMES is
242 passed to restore_file() helper. */
244 report_revisions_and_depths(svn_wc__db_t *db,
245 const char *dir_abspath,
246 const char *report_relpath,
247 svn_revnum_t dir_rev,
248 const char *dir_repos_relpath,
249 const char *dir_repos_root,
250 svn_depth_t dir_depth,
251 const svn_ra_reporter3_t *reporter,
253 svn_boolean_t restore_files,
255 svn_boolean_t honor_depth_exclude,
256 svn_boolean_t depth_compatibility_trick,
257 svn_boolean_t report_everything,
258 svn_boolean_t use_commit_times,
259 svn_cancel_func_t cancel_func,
261 svn_wc_notify_func2_t notify_func,
263 apr_pool_t *scratch_pool)
265 apr_hash_t *base_children;
267 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
268 apr_hash_index_t *hi;
272 /* Get both the SVN Entries and the actual on-disk entries. Also
273 notice that we're picking up hidden entries too (read_children never
275 SVN_ERR(svn_wc__db_base_get_children_info(&base_children, db, dir_abspath,
276 scratch_pool, iterpool));
280 err = svn_io_get_dirents3(&dirents, dir_abspath, TRUE,
281 scratch_pool, scratch_pool);
283 if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
284 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
286 svn_error_clear(err);
287 /* There is no directory, and if we could create the directory
288 we would have already created it when walking the parent
290 restore_files = FALSE;
299 /*** Do the real reporting and recursing. ***/
301 /* Looping over current directory's BASE children: */
302 for (hi = apr_hash_first(scratch_pool, base_children);
304 hi = apr_hash_next(hi))
306 const char *child = apr_hash_this_key(hi);
307 const char *this_report_relpath;
308 const char *this_abspath;
309 svn_boolean_t this_switched = FALSE;
310 struct svn_wc__db_base_info_t *ths = apr_hash_this_val(hi);
313 SVN_ERR(cancel_func(cancel_baton));
315 /* Clear the iteration subpool here because the loop has a bunch
316 of 'continue' jump statements. */
317 svn_pool_clear(iterpool);
319 /* Compute the paths and URLs we need. */
320 this_report_relpath = svn_relpath_join(report_relpath, child, iterpool);
321 this_abspath = svn_dirent_join(dir_abspath, child, iterpool);
323 /*** File Externals **/
324 if (ths->update_root)
326 /* File externals are ... special. We ignore them. */;
330 /* First check for exclusion */
331 if (ths->status == svn_wc__db_status_excluded)
333 if (honor_depth_exclude)
335 /* Report the excluded path, no matter whether report_everything
336 flag is set. Because the report_everything flag indicates
337 that the server will treat the wc as empty and thus push
338 full content of the files/subdirs. But we want to prevent the
339 server from pushing the full content of this_path at us. */
341 /* The server does not support link_path report on excluded
342 path. We explicitly prohibit this situation in
343 svn_wc_crop_tree(). */
344 SVN_ERR(reporter->set_path(report_baton,
354 /* We want to pull in the excluded target. So, report it as
355 deleted, and server will respond properly. */
356 if (! report_everything)
357 SVN_ERR(reporter->delete_path(report_baton,
358 this_report_relpath, iterpool));
363 /*** The Big Tests: ***/
364 if (ths->status == svn_wc__db_status_server_excluded
365 || ths->status == svn_wc__db_status_not_present)
367 /* If the entry is 'absent' or 'not-present', make sure the server
369 ...unless we're reporting everything, in which case we're
370 going to report it missing later anyway.
372 This instructs the server to send it back to us, if it is
373 now available (an addition after a not-present state), or if
374 it is now authorized (change in authz for the absent item). */
375 if (! report_everything)
376 SVN_ERR(reporter->delete_path(report_baton, this_report_relpath,
381 /* Is the entry NOT on the disk? We may be able to restore it. */
383 && svn_hash_gets(dirents, child) == NULL)
385 svn_wc__db_status_t wrk_status;
386 svn_node_kind_t wrk_kind;
387 const svn_checksum_t *checksum;
388 svn_boolean_t conflicted;
390 SVN_ERR(svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL,
391 NULL, NULL, NULL, NULL, NULL, NULL,
392 &checksum, NULL, NULL, NULL, NULL, NULL,
393 NULL, NULL, NULL, NULL, &conflicted,
394 NULL, NULL, NULL, NULL, NULL, NULL,
395 db, this_abspath, iterpool, iterpool));
397 if ((wrk_status == svn_wc__db_status_normal
398 || wrk_status == svn_wc__db_status_added
399 || wrk_status == svn_wc__db_status_incomplete)
400 && (wrk_kind == svn_node_dir || checksum))
402 svn_node_kind_t dirent_kind;
404 /* It is possible on a case insensitive system that the
405 entry is not really missing, but just cased incorrectly.
406 In this case we can't overwrite it with the pristine
408 SVN_ERR(svn_io_check_path(this_abspath, &dirent_kind, iterpool));
410 if (dirent_kind == svn_node_none)
412 SVN_ERR(restore_node(db, this_abspath, wrk_kind,
413 conflicted, use_commit_times,
414 cancel_func, cancel_baton,
415 notify_func, notify_baton, iterpool));
420 /* And finally prepare for reporting */
421 if (!ths->repos_relpath)
423 ths->repos_relpath = svn_relpath_join(dir_repos_relpath, child,
428 const char *childname
429 = svn_relpath_skip_ancestor(dir_repos_relpath, ths->repos_relpath);
431 if (childname == NULL || strcmp(childname, child) != 0)
433 this_switched = TRUE;
437 /* Tweak THIS_DEPTH to a useful value. */
438 if (ths->depth == svn_depth_unknown)
439 ths->depth = svn_depth_infinity;
442 if (ths->kind == svn_node_file
443 || ths->kind == svn_node_symlink)
445 if (report_everything)
447 /* Report the file unconditionally, one way or another. */
449 SVN_ERR(reporter->link_path(report_baton,
451 svn_path_url_add_component2(
453 ths->repos_relpath, iterpool),
457 ths->lock ? ths->lock->token : NULL,
460 SVN_ERR(reporter->set_path(report_baton,
465 ths->lock ? ths->lock->token : NULL,
469 /* Possibly report a disjoint URL ... */
470 else if (this_switched)
471 SVN_ERR(reporter->link_path(report_baton,
473 svn_path_url_add_component2(
475 ths->repos_relpath, iterpool),
479 ths->lock ? ths->lock->token : NULL,
481 /* ... or perhaps just a differing revision or lock token,
482 or the mere presence of the file in a depth-empty dir. */
483 else if (ths->revnum != dir_rev
485 || dir_depth == svn_depth_empty)
486 SVN_ERR(reporter->set_path(report_baton,
491 ths->lock ? ths->lock->token : NULL,
493 } /* end file case */
495 /*** Directories (in recursive mode) ***/
496 else if (ths->kind == svn_node_dir
497 && (depth > svn_depth_files
498 || depth == svn_depth_unknown))
500 svn_boolean_t is_incomplete;
501 svn_boolean_t start_empty;
502 svn_depth_t report_depth = ths->depth;
504 is_incomplete = (ths->status == svn_wc__db_status_incomplete);
505 start_empty = is_incomplete;
507 if (!SVN_DEPTH_IS_RECURSIVE(depth))
508 report_depth = svn_depth_empty;
510 /* When a <= 1.6 working copy is upgraded without some of its
511 subdirectories we miss some information in the database. If we
512 report the revision as -1, the update editor will receive an
513 add_directory() while it still knows the directory.
515 This would raise strange tree conflicts and probably assertions
516 as it would a BASE vs BASE conflict */
517 if (is_incomplete && !SVN_IS_VALID_REVNUM(ths->revnum))
518 ths->revnum = dir_rev;
520 if (depth_compatibility_trick
521 && ths->depth <= svn_depth_files
522 && depth > ths->depth)
527 if (report_everything)
529 /* Report the dir unconditionally, one way or another... */
531 SVN_ERR(reporter->link_path(report_baton,
533 svn_path_url_add_component2(
535 ths->repos_relpath, iterpool),
539 ths->lock ? ths->lock->token
543 SVN_ERR(reporter->set_path(report_baton,
548 ths->lock ? ths->lock->token : NULL,
551 else if (this_switched)
553 /* ...or possibly report a disjoint URL ... */
554 SVN_ERR(reporter->link_path(report_baton,
556 svn_path_url_add_component2(
558 ths->repos_relpath, iterpool),
562 ths->lock ? ths->lock->token : NULL,
565 else if (ths->revnum != dir_rev
568 || dir_depth == svn_depth_empty
569 || dir_depth == svn_depth_files
570 || (dir_depth == svn_depth_immediates
571 && ths->depth != svn_depth_empty)
572 || (ths->depth < svn_depth_infinity
573 && SVN_DEPTH_IS_RECURSIVE(depth)))
575 /* ... or perhaps just a differing revision, lock token,
576 incomplete subdir, the mere presence of the directory
577 in a depth-empty or depth-files dir, or if the parent
578 dir is at depth-immediates but the child is not at
579 depth-empty. Also describe shallow subdirs if we are
580 trying to set depth to infinity. */
581 SVN_ERR(reporter->set_path(report_baton,
586 ths->lock ? ths->lock->token : NULL,
590 /* Finally, recurse if necessary and appropriate. */
591 if (SVN_DEPTH_IS_RECURSIVE(depth))
593 const char *repos_relpath = ths->repos_relpath;
595 if (repos_relpath == NULL)
597 repos_relpath = svn_relpath_join(dir_repos_relpath, child,
601 SVN_ERR(report_revisions_and_depths(db,
608 reporter, report_baton,
609 restore_files, depth,
611 depth_compatibility_trick,
614 cancel_func, cancel_baton,
615 notify_func, notify_baton,
618 } /* end directory case */
619 } /* end main entries loop */
621 /* We're done examining this dir's entries, so free everything. */
622 svn_pool_destroy(iterpool);
628 /*------------------------------------------------------------------*/
629 /*** Public Interfaces ***/
633 svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx,
634 const char *local_abspath,
635 const svn_ra_reporter3_t *reporter,
637 svn_boolean_t restore_files,
639 svn_boolean_t honor_depth_exclude,
640 svn_boolean_t depth_compatibility_trick,
641 svn_boolean_t use_commit_times,
642 svn_cancel_func_t cancel_func,
644 svn_wc_notify_func2_t notify_func,
646 apr_pool_t *scratch_pool)
648 svn_wc__db_t *db = wc_ctx->db;
649 svn_error_t *fserr, *err;
650 svn_revnum_t target_rev = SVN_INVALID_REVNUM;
651 svn_boolean_t start_empty;
652 svn_wc__db_status_t status;
653 svn_node_kind_t target_kind;
654 const char *repos_relpath, *repos_root_url;
655 svn_depth_t target_depth;
656 svn_wc__db_lock_t *target_lock;
657 svn_node_kind_t disk_kind;
658 svn_depth_t report_depth;
659 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
661 /* Get the base rev, which is the first revnum that entries will be
662 compared to, and some other WC info about the target. */
663 err = svn_wc__db_base_get_info(&status, &target_kind, &target_rev,
664 &repos_relpath, &repos_root_url,
665 NULL, NULL, NULL, NULL, &target_depth,
666 NULL, NULL, &target_lock,
668 db, local_abspath, scratch_pool,
672 || (status != svn_wc__db_status_normal
673 && status != svn_wc__db_status_incomplete))
675 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
676 return svn_error_trace(err);
678 svn_error_clear(err);
680 /* We don't know about this node, so all we have to do is tell
681 the reporter that we don't know this node.
683 But first we have to start the report by sending some basic
684 information for the root. */
686 if (depth == svn_depth_unknown)
687 depth = svn_depth_infinity;
689 SVN_ERR(reporter->set_path(report_baton, "", 0, depth, FALSE,
690 NULL, scratch_pool));
691 SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
693 /* Finish the report, which causes the update editor to be
695 SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
700 if (target_depth == svn_depth_unknown)
701 target_depth = svn_depth_infinity;
703 start_empty = (status == svn_wc__db_status_incomplete);
704 if (depth_compatibility_trick
705 && target_depth <= svn_depth_immediates
706 && depth > target_depth)
712 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
714 disk_kind = svn_node_unknown;
716 /* Determine if there is a missing node that should be restored */
718 && disk_kind == svn_node_none)
720 svn_wc__db_status_t wrk_status;
721 svn_node_kind_t wrk_kind;
722 const svn_checksum_t *checksum;
723 svn_boolean_t conflicted;
725 err = svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL, NULL,
726 NULL, NULL, NULL, NULL, NULL, &checksum, NULL,
727 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
728 NULL, &conflicted, NULL, NULL, NULL, NULL,
731 scratch_pool, scratch_pool);
734 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
736 svn_error_clear(err);
737 wrk_status = svn_wc__db_status_not_present;
738 wrk_kind = svn_node_file;
743 if ((wrk_status == svn_wc__db_status_normal
744 || wrk_status == svn_wc__db_status_added
745 || wrk_status == svn_wc__db_status_incomplete)
746 && (wrk_kind == svn_node_dir || checksum))
748 SVN_ERR(restore_node(wc_ctx->db, local_abspath,
749 wrk_kind, conflicted, use_commit_times,
750 cancel_func, cancel_baton,
751 notify_func, notify_baton,
757 report_depth = target_depth;
759 if (honor_depth_exclude
760 && depth != svn_depth_unknown
761 && depth < target_depth)
762 report_depth = depth;
764 /* The first call to the reporter merely informs it that the
765 top-level directory being updated is at BASE_REV. Its PATH
766 argument is ignored. */
767 SVN_ERR(reporter->set_path(report_baton, "", target_rev, report_depth,
768 start_empty, NULL, scratch_pool));
770 if (target_kind == svn_node_dir)
772 if (depth != svn_depth_empty)
774 /* Recursively crawl ROOT_DIRECTORY and report differing
776 err = report_revisions_and_depths(wc_ctx->db,
783 reporter, report_baton,
784 restore_files, depth,
786 depth_compatibility_trick,
789 cancel_func, cancel_baton,
790 notify_func, notify_baton,
797 else if (target_kind == svn_node_file || target_kind == svn_node_symlink)
799 const char *parent_abspath, *base;
800 svn_wc__db_status_t parent_status;
801 const char *parent_repos_relpath;
803 svn_dirent_split(&parent_abspath, &base, local_abspath,
806 /* We can assume a file is in the same repository as its parent
807 directory, so we only look at the relpath. */
808 err = svn_wc__db_base_get_info(&parent_status, NULL, NULL,
809 &parent_repos_relpath, NULL, NULL, NULL,
810 NULL, NULL, NULL, NULL, NULL, NULL,
813 scratch_pool, scratch_pool);
818 if (strcmp(repos_relpath,
819 svn_relpath_join(parent_repos_relpath, base,
822 /* This file is disjoint with respect to its parent
823 directory. Since we are looking at the actual target of
824 the report (not some file in a subdirectory of a target
825 directory), and that target is a file, we need to pass an
826 empty string to link_path. */
827 err = reporter->link_path(report_baton,
829 svn_path_url_add_component2(
836 target_lock ? target_lock->token : NULL,
841 else if (target_lock)
843 /* If this entry is a file node, we just want to report that
844 node's revision. Since we are looking at the actual target
845 of the report (not some file in a subdirectory of a target
846 directory), and that target is a file, we need to pass an
847 empty string to set_path. */
848 err = reporter->set_path(report_baton, "", target_rev,
851 target_lock ? target_lock->token : NULL,
858 /* Finish the report, which causes the update editor to be driven. */
859 return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
862 /* Clean up the fs transaction. */
863 if ((fserr = reporter->abort_report(report_baton, scratch_pool)))
865 fserr = svn_error_quick_wrap(fserr, _("Error aborting report"));
866 svn_error_compose(err, fserr);
868 return svn_error_trace(err);
871 /*** Copying stream ***/
873 /* A copying stream is a bit like the unix tee utility:
875 * It reads the SOURCE when asked for data and while returning it,
876 * also writes the same data to TARGET.
878 struct copying_stream_baton
880 /* Stream to read input from. */
881 svn_stream_t *source;
883 /* Stream to write all data read to. */
884 svn_stream_t *target;
890 read_handler_copy(void *baton, char *buffer, apr_size_t *len)
892 struct copying_stream_baton *btn = baton;
894 SVN_ERR(svn_stream_read_full(btn->source, buffer, len));
896 return svn_stream_write(btn->target, buffer, len);
901 close_handler_copy(void *baton)
903 struct copying_stream_baton *btn = baton;
905 SVN_ERR(svn_stream_close(btn->target));
906 return svn_stream_close(btn->source);
910 /* Return a stream - allocated in POOL - which reads its input
911 * from SOURCE and, while returning that to the caller, at the
912 * same time writes that to TARGET.
914 static svn_stream_t *
915 copying_stream(svn_stream_t *source,
916 svn_stream_t *target,
919 struct copying_stream_baton *baton;
920 svn_stream_t *stream;
922 baton = apr_palloc(pool, sizeof (*baton));
923 baton->source = source;
924 baton->target = target;
926 stream = svn_stream_create(baton, pool);
927 svn_stream_set_read2(stream, NULL /* only full read support */,
929 svn_stream_set_close(stream, close_handler_copy);
935 /* Set *STREAM to a stream from which the caller can read the pristine text
936 * of the working version of the file at LOCAL_ABSPATH. If the working
937 * version of LOCAL_ABSPATH has no pristine text because it is locally
938 * added, set *STREAM to an empty stream. If the working version of
939 * LOCAL_ABSPATH is not a file, return an error.
941 * Set *EXPECTED_MD5_CHECKSUM to the recorded MD5 checksum.
943 * Arrange for the actual checksum of the text to be calculated and written
944 * into *ACTUAL_MD5_CHECKSUM when the stream is read.
947 read_and_checksum_pristine_text(svn_stream_t **stream,
948 const svn_checksum_t **expected_md5_checksum,
949 svn_checksum_t **actual_md5_checksum,
951 const char *local_abspath,
952 apr_pool_t *result_pool,
953 apr_pool_t *scratch_pool)
955 svn_stream_t *base_stream;
957 SVN_ERR(svn_wc__get_pristine_contents(&base_stream, NULL, db, local_abspath,
958 result_pool, scratch_pool));
959 if (base_stream == NULL)
961 base_stream = svn_stream_empty(result_pool);
962 *expected_md5_checksum = NULL;
963 *actual_md5_checksum = NULL;
967 const svn_checksum_t *expected_md5;
969 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
970 NULL, NULL, NULL, NULL, &expected_md5,
971 NULL, NULL, NULL, NULL, NULL, NULL,
972 NULL, NULL, NULL, NULL, NULL, NULL,
973 NULL, NULL, NULL, NULL,
975 result_pool, scratch_pool));
976 if (expected_md5 == NULL)
977 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
978 _("Pristine checksum for file '%s' is missing"),
979 svn_dirent_local_style(local_abspath,
981 if (expected_md5->kind != svn_checksum_md5)
982 SVN_ERR(svn_wc__db_pristine_get_md5(&expected_md5, db, local_abspath,
984 result_pool, scratch_pool));
985 *expected_md5_checksum = expected_md5;
987 /* Arrange to set ACTUAL_MD5_CHECKSUM to the MD5 of what is *actually*
988 found when the base stream is read. */
989 base_stream = svn_stream_checksummed2(base_stream, actual_md5_checksum,
990 NULL, svn_checksum_md5, TRUE,
994 *stream = base_stream;
1000 svn_wc__internal_transmit_text_deltas(const char **tempfile,
1001 const svn_checksum_t **new_text_base_md5_checksum,
1002 const svn_checksum_t **new_text_base_sha1_checksum,
1004 const char *local_abspath,
1005 svn_boolean_t fulltext,
1006 const svn_delta_editor_t *editor,
1008 apr_pool_t *result_pool,
1009 apr_pool_t *scratch_pool)
1011 svn_txdelta_window_handler_t handler;
1013 const svn_checksum_t *expected_md5_checksum; /* recorded MD5 of BASE_S. */
1014 svn_checksum_t *verify_checksum; /* calc'd MD5 of BASE_STREAM */
1015 svn_checksum_t *local_md5_checksum; /* calc'd MD5 of LOCAL_STREAM */
1016 svn_checksum_t *local_sha1_checksum; /* calc'd SHA1 of LOCAL_STREAM */
1017 svn_wc__db_install_data_t *install_data = NULL;
1020 svn_stream_t *base_stream; /* delta source */
1021 svn_stream_t *local_stream; /* delta target: LOCAL_ABSPATH transl. to NF */
1023 /* Translated input */
1024 SVN_ERR(svn_wc__internal_translated_stream(&local_stream, db,
1025 local_abspath, local_abspath,
1026 SVN_WC_TRANSLATE_TO_NF,
1027 scratch_pool, scratch_pool));
1029 /* If the caller wants a copy of the working file translated to
1030 * repository-normal form, make the copy by tee-ing the stream and set
1031 * *TEMPFILE to the path to it. This is only needed for the 1.6 API,
1032 * 1.7 doesn't set TEMPFILE. Even when using the 1.6 API this file
1033 * is not used by the functions that would have used it when using
1034 * the 1.6 code. It's possible that 3rd party users (if there are any)
1035 * might expect this file to be a text-base. */
1038 svn_stream_t *tempstream;
1040 /* It can't be the same location as in 1.6 because the admin directory
1041 no longer exists. */
1042 SVN_ERR(svn_stream_open_unique(&tempstream, tempfile,
1043 NULL, svn_io_file_del_none,
1044 result_pool, scratch_pool));
1046 /* Wrap the translated stream with a new stream that writes the
1047 translated contents into the new text base file as we read from it.
1048 Note that the new text base file will be closed when the new stream
1050 local_stream = copying_stream(local_stream, tempstream, scratch_pool);
1052 if (new_text_base_sha1_checksum)
1054 svn_stream_t *new_pristine_stream;
1056 SVN_ERR(svn_wc__db_pristine_prepare_install(&new_pristine_stream,
1058 &local_sha1_checksum, NULL,
1060 scratch_pool, scratch_pool));
1061 local_stream = copying_stream(local_stream, new_pristine_stream,
1065 /* If sending a full text is requested, or if there is no pristine text
1066 * (e.g. the node is locally added), then set BASE_STREAM to an empty
1067 * stream and leave EXPECTED_MD5_CHECKSUM and VERIFY_CHECKSUM as NULL.
1069 * Otherwise, set BASE_STREAM to a stream providing the base (source) text
1070 * for the delta, set EXPECTED_MD5_CHECKSUM to its stored MD5 checksum,
1071 * and arrange for its VERIFY_CHECKSUM to be calculated later. */
1074 /* We will be computing a delta against the pristine contents */
1075 /* We need the expected checksum to be an MD-5 checksum rather than a
1076 * SHA-1 because we want to pass it to apply_textdelta(). */
1077 SVN_ERR(read_and_checksum_pristine_text(&base_stream,
1078 &expected_md5_checksum,
1081 scratch_pool, scratch_pool));
1085 /* Send a fulltext. */
1086 base_stream = svn_stream_empty(scratch_pool);
1087 expected_md5_checksum = NULL;
1088 verify_checksum = NULL;
1091 /* Tell the editor that we're about to apply a textdelta to the
1092 file baton; the editor returns to us a window consumer and baton. */
1094 /* apply_textdelta() is working against a base with this checksum */
1095 const char *base_digest_hex = NULL;
1097 if (expected_md5_checksum)
1098 /* ### Why '..._display()'? expected_md5_checksum should never be all-
1099 * zero, but if it is, we would want to pass NULL not an all-zero
1100 * digest to apply_textdelta(), wouldn't we? */
1101 base_digest_hex = svn_checksum_to_cstring_display(expected_md5_checksum,
1104 SVN_ERR(editor->apply_textdelta(file_baton, base_digest_hex, scratch_pool,
1105 &handler, &wh_baton));
1108 /* Run diff processing, throwing windows at the handler. */
1109 err = svn_txdelta_run(base_stream, local_stream,
1111 svn_checksum_md5, &local_md5_checksum,
1113 scratch_pool, scratch_pool);
1115 /* Close the two streams to force writing the digest */
1116 err2 = svn_stream_close(base_stream);
1119 /* Set verify_checksum to NULL if svn_stream_close() returns error
1120 because checksum will be uninitialized in this case. */
1121 verify_checksum = NULL;
1122 err = svn_error_compose_create(err, err2);
1125 err = svn_error_compose_create(err, svn_stream_close(local_stream));
1127 /* If we have an error, it may be caused by a corrupt text base,
1128 so check the checksum. */
1129 if (expected_md5_checksum && verify_checksum
1130 && !svn_checksum_match(expected_md5_checksum, verify_checksum))
1132 /* The entry checksum does not match the actual text
1133 base checksum. Extreme badness. Of course,
1134 theoretically we could just switch to
1135 fulltext transmission here, and everything would
1136 work fine; after all, we're going to replace the
1137 text base with a new one in a moment anyway, and
1138 we'd fix the checksum then. But it's better to
1139 error out. People should know that their text
1140 bases are getting corrupted, so they can
1141 investigate. Other commands could be affected,
1142 too, such as `svn diff'. */
1145 err = svn_error_compose_create(
1147 svn_io_remove_file2(*tempfile, TRUE, scratch_pool));
1149 err = svn_error_compose_create(
1150 svn_checksum_mismatch_err(expected_md5_checksum, verify_checksum,
1152 _("Checksum mismatch for text base of '%s'"),
1153 svn_dirent_local_style(local_abspath,
1157 return svn_error_create(SVN_ERR_WC_CORRUPT_TEXT_BASE, err, NULL);
1160 /* Now, handle that delta transmission error if any, so we can stop
1161 thinking about it after this point. */
1162 SVN_ERR_W(err, apr_psprintf(scratch_pool,
1163 _("While preparing '%s' for commit"),
1164 svn_dirent_local_style(local_abspath,
1167 if (new_text_base_md5_checksum)
1168 *new_text_base_md5_checksum = svn_checksum_dup(local_md5_checksum,
1170 if (new_text_base_sha1_checksum)
1172 SVN_ERR(svn_wc__db_pristine_install(install_data,
1173 local_sha1_checksum,
1176 *new_text_base_sha1_checksum = svn_checksum_dup(local_sha1_checksum,
1180 /* Close the file baton, and get outta here. */
1181 return svn_error_trace(
1182 editor->close_file(file_baton,
1183 svn_checksum_to_cstring(local_md5_checksum,
1189 svn_wc_transmit_text_deltas3(const svn_checksum_t **new_text_base_md5_checksum,
1190 const svn_checksum_t **new_text_base_sha1_checksum,
1191 svn_wc_context_t *wc_ctx,
1192 const char *local_abspath,
1193 svn_boolean_t fulltext,
1194 const svn_delta_editor_t *editor,
1196 apr_pool_t *result_pool,
1197 apr_pool_t *scratch_pool)
1199 return svn_wc__internal_transmit_text_deltas(NULL,
1200 new_text_base_md5_checksum,
1201 new_text_base_sha1_checksum,
1202 wc_ctx->db, local_abspath,
1204 file_baton, result_pool,
1209 svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db,
1210 const char *local_abspath,
1211 const svn_delta_editor_t *editor,
1213 apr_pool_t *scratch_pool)
1215 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1217 apr_array_header_t *propmods;
1218 svn_node_kind_t kind;
1220 SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath,
1221 FALSE /* allow_missing */,
1222 FALSE /* show_deleted */,
1223 FALSE /* show_hidden */,
1226 if (kind == svn_node_none)
1227 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1228 _("The node '%s' was not found."),
1229 svn_dirent_local_style(local_abspath, iterpool));
1231 /* Get an array of local changes by comparing the hashes. */
1232 SVN_ERR(svn_wc__internal_propdiff(&propmods, NULL, db, local_abspath,
1233 scratch_pool, iterpool));
1235 /* Apply each local change to the baton */
1236 for (i = 0; i < propmods->nelts; i++)
1238 const svn_prop_t *p = &APR_ARRAY_IDX(propmods, i, svn_prop_t);
1240 svn_pool_clear(iterpool);
1242 if (kind == svn_node_file)
1243 SVN_ERR(editor->change_file_prop(baton, p->name, p->value,
1246 SVN_ERR(editor->change_dir_prop(baton, p->name, p->value,
1250 svn_pool_destroy(iterpool);
1251 return SVN_NO_ERROR;
1255 svn_wc_transmit_prop_deltas2(svn_wc_context_t *wc_ctx,
1256 const char *local_abspath,
1257 const svn_delta_editor_t *editor,
1259 apr_pool_t *scratch_pool)
1261 return svn_wc__internal_transmit_prop_deltas(wc_ctx->db, local_abspath,
1262 editor, baton, scratch_pool);