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 apr_pool_t *scratch_pool)
74 svn_skel_t *work_item;
76 SVN_ERR(svn_wc__wq_build_file_install(&work_item,
78 NULL /* source_abspath */,
80 TRUE /* record_fileinfo */,
81 scratch_pool, scratch_pool));
82 /* ### we need an existing path for wq_add. not entirely WRI_ABSPATH yet */
83 SVN_ERR(svn_wc__db_wq_add(db,
84 svn_dirent_dirname(local_abspath, scratch_pool),
85 work_item, scratch_pool));
87 /* Run the work item immediately. */
88 SVN_ERR(svn_wc__wq_run(db, local_abspath,
89 NULL, NULL, /* ### nice to have cancel_func/baton */
92 /* Remove any text conflict */
93 if (mark_resolved_text_conflict)
94 SVN_ERR(svn_wc__mark_resolved_text_conflict(db, local_abspath, scratch_pool));
100 svn_wc_restore(svn_wc_context_t *wc_ctx,
101 const char *local_abspath,
102 svn_boolean_t use_commit_times,
103 apr_pool_t *scratch_pool)
105 svn_wc__db_status_t status;
106 svn_node_kind_t kind;
107 svn_node_kind_t disk_kind;
108 const svn_checksum_t *checksum;
110 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
112 if (disk_kind != svn_node_none)
113 return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
114 _("The existing node '%s' can not be restored."),
115 svn_dirent_local_style(local_abspath,
118 SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
119 NULL, NULL, NULL, &checksum, NULL, NULL, NULL, NULL,
120 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
121 NULL, NULL, NULL, NULL,
122 wc_ctx->db, local_abspath,
123 scratch_pool, scratch_pool));
125 if (status != svn_wc__db_status_normal
126 && !((status == svn_wc__db_status_added
127 || status == svn_wc__db_status_incomplete)
128 && (kind == svn_node_dir
129 || (kind == svn_node_file && checksum != NULL)
130 /* || (kind == svn_node_symlink && target)*/)))
132 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
133 _("The node '%s' can not be restored."),
134 svn_dirent_local_style(local_abspath,
138 if (kind == svn_node_file || kind == svn_node_symlink)
139 SVN_ERR(restore_file(wc_ctx->db, local_abspath, use_commit_times,
140 FALSE /*mark_resolved_text_conflict*/,
143 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
148 /* Try to restore LOCAL_ABSPATH of node type KIND and if successfull,
149 notify that the node is restored. Use DB for accessing the working copy.
150 If USE_COMMIT_TIMES is set, then set working file's timestamp to
153 This function does all temporary allocations in SCRATCH_POOL
156 restore_node(svn_wc__db_t *db,
157 const char *local_abspath,
158 svn_node_kind_t kind,
159 svn_boolean_t use_commit_times,
160 svn_wc_notify_func2_t notify_func,
162 apr_pool_t *scratch_pool)
164 if (kind == svn_node_file || kind == svn_node_symlink)
166 /* Recreate file from text-base; mark any text conflict as resolved */
167 SVN_ERR(restore_file(db, local_abspath, use_commit_times,
168 TRUE /*mark_resolved_text_conflict*/,
171 else if (kind == svn_node_dir)
173 /* Recreating a directory is just a mkdir */
174 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
177 /* ... report the restoration to the caller. */
178 if (notify_func != NULL)
180 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
181 svn_wc_notify_restore,
183 notify->kind = svn_node_file;
184 (*notify_func)(notify_baton, notify, scratch_pool);
190 /* The recursive crawler that describes a mixed-revision working
191 copy to an RA layer. Used to initiate updates.
193 This is a depth-first recursive walk of the children of DIR_ABSPATH
194 (not including DIR_ABSPATH itself) using DB. Look at each node and
195 check if its revision is different than DIR_REV. If so, report this
196 fact to REPORTER. If a node has a different URL than expected, or
197 a different depth than its parent, report that to REPORTER.
199 Report DIR_ABSPATH to the reporter as REPORT_RELPATH.
201 Alternatively, if REPORT_EVERYTHING is set, then report all
202 children unconditionally.
204 DEPTH is actually the *requested* depth for the update-like
205 operation for which we are reporting working copy state. However,
206 certain requested depths affect the depth of the report crawl. For
207 example, if the requested depth is svn_depth_empty, there's no
208 point descending into subdirs, no matter what their depths. So:
210 If DEPTH is svn_depth_empty, don't report any files and don't
211 descend into any subdirs. If svn_depth_files, report files but
212 still don't descend into subdirs. If svn_depth_immediates, report
213 files, and report subdirs themselves but not their entries. If
214 svn_depth_infinity or svn_depth_unknown, report everything all the
215 way down. (That last sentence might sound counterintuitive, but
216 since you can't go deeper than the local ambient depth anyway,
217 requesting svn_depth_infinity really means "as deep as the various
218 parts of this working copy go". Of course, the information that
219 comes back from the server will be different for svn_depth_unknown
220 than for svn_depth_infinity.)
222 DIR_REPOS_RELPATH, DIR_REPOS_ROOT and DIR_DEPTH are the repository
223 relative path, the repository root and depth stored on the directory,
224 passed here to avoid another database query.
226 DEPTH_COMPATIBILITY_TRICK means the same thing here as it does
227 in svn_wc_crawl_revisions5().
229 If RESTORE_FILES is set, then unexpectedly missing working files
230 will be restored from text-base and NOTIFY_FUNC/NOTIFY_BATON
231 will be called to report the restoration. USE_COMMIT_TIMES is
232 passed to restore_file() helper. */
234 report_revisions_and_depths(svn_wc__db_t *db,
235 const char *dir_abspath,
236 const char *report_relpath,
237 svn_revnum_t dir_rev,
238 const char *dir_repos_relpath,
239 const char *dir_repos_root,
240 svn_depth_t dir_depth,
241 const svn_ra_reporter3_t *reporter,
243 svn_boolean_t restore_files,
245 svn_boolean_t honor_depth_exclude,
246 svn_boolean_t depth_compatibility_trick,
247 svn_boolean_t report_everything,
248 svn_boolean_t use_commit_times,
249 svn_cancel_func_t cancel_func,
251 svn_wc_notify_func2_t notify_func,
253 apr_pool_t *scratch_pool)
255 apr_hash_t *base_children;
257 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
258 apr_hash_index_t *hi;
262 /* Get both the SVN Entries and the actual on-disk entries. Also
263 notice that we're picking up hidden entries too (read_children never
265 SVN_ERR(svn_wc__db_base_get_children_info(&base_children, db, dir_abspath,
266 scratch_pool, iterpool));
270 err = svn_io_get_dirents3(&dirents, dir_abspath, TRUE,
271 scratch_pool, scratch_pool);
273 if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
274 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
276 svn_error_clear(err);
277 /* There is no directory, and if we could create the directory
278 we would have already created it when walking the parent
280 restore_files = FALSE;
289 /*** Do the real reporting and recursing. ***/
291 /* Looping over current directory's BASE children: */
292 for (hi = apr_hash_first(scratch_pool, base_children);
294 hi = apr_hash_next(hi))
296 const char *child = svn__apr_hash_index_key(hi);
297 const char *this_report_relpath;
298 const char *this_abspath;
299 svn_boolean_t this_switched = FALSE;
300 struct svn_wc__db_base_info_t *ths = svn__apr_hash_index_val(hi);
303 SVN_ERR(cancel_func(cancel_baton));
305 /* Clear the iteration subpool here because the loop has a bunch
306 of 'continue' jump statements. */
307 svn_pool_clear(iterpool);
309 /* Compute the paths and URLs we need. */
310 this_report_relpath = svn_relpath_join(report_relpath, child, iterpool);
311 this_abspath = svn_dirent_join(dir_abspath, child, iterpool);
313 /*** File Externals **/
314 if (ths->update_root)
316 /* File externals are ... special. We ignore them. */;
320 /* First check for exclusion */
321 if (ths->status == svn_wc__db_status_excluded)
323 if (honor_depth_exclude)
325 /* Report the excluded path, no matter whether report_everything
326 flag is set. Because the report_everything flag indicates
327 that the server will treat the wc as empty and thus push
328 full content of the files/subdirs. But we want to prevent the
329 server from pushing the full content of this_path at us. */
331 /* The server does not support link_path report on excluded
332 path. We explicitly prohibit this situation in
333 svn_wc_crop_tree(). */
334 SVN_ERR(reporter->set_path(report_baton,
344 /* We want to pull in the excluded target. So, report it as
345 deleted, and server will respond properly. */
346 if (! report_everything)
347 SVN_ERR(reporter->delete_path(report_baton,
348 this_report_relpath, iterpool));
353 /*** The Big Tests: ***/
354 if (ths->status == svn_wc__db_status_server_excluded
355 || ths->status == svn_wc__db_status_not_present)
357 /* If the entry is 'absent' or 'not-present', make sure the server
359 ...unless we're reporting everything, in which case we're
360 going to report it missing later anyway.
362 This instructs the server to send it back to us, if it is
363 now available (an addition after a not-present state), or if
364 it is now authorized (change in authz for the absent item). */
365 if (! report_everything)
366 SVN_ERR(reporter->delete_path(report_baton, this_report_relpath,
371 /* Is the entry NOT on the disk? We may be able to restore it. */
373 && svn_hash_gets(dirents, child) == NULL)
375 svn_wc__db_status_t wrk_status;
376 svn_node_kind_t wrk_kind;
377 const svn_checksum_t *checksum;
379 SVN_ERR(svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL,
380 NULL, NULL, NULL, NULL, NULL, NULL,
381 &checksum, NULL, NULL, NULL, NULL, NULL,
382 NULL, NULL, NULL, NULL, NULL, NULL,
383 NULL, NULL, NULL, NULL, NULL,
384 db, this_abspath, iterpool, iterpool));
386 if ((wrk_status == svn_wc__db_status_normal
387 || wrk_status == svn_wc__db_status_added
388 || wrk_status == svn_wc__db_status_incomplete)
389 && (wrk_kind == svn_node_dir || checksum))
391 svn_node_kind_t dirent_kind;
393 /* It is possible on a case insensitive system that the
394 entry is not really missing, but just cased incorrectly.
395 In this case we can't overwrite it with the pristine
397 SVN_ERR(svn_io_check_path(this_abspath, &dirent_kind, iterpool));
399 if (dirent_kind == svn_node_none)
401 SVN_ERR(restore_node(db, this_abspath, wrk_kind,
402 use_commit_times, notify_func,
403 notify_baton, iterpool));
408 /* And finally prepare for reporting */
409 if (!ths->repos_relpath)
411 ths->repos_relpath = svn_relpath_join(dir_repos_relpath, child,
416 const char *childname
417 = svn_relpath_skip_ancestor(dir_repos_relpath, ths->repos_relpath);
419 if (childname == NULL || strcmp(childname, child) != 0)
421 this_switched = TRUE;
425 /* Tweak THIS_DEPTH to a useful value. */
426 if (ths->depth == svn_depth_unknown)
427 ths->depth = svn_depth_infinity;
430 if (ths->kind == svn_node_file
431 || ths->kind == svn_node_symlink)
433 if (report_everything)
435 /* Report the file unconditionally, one way or another. */
437 SVN_ERR(reporter->link_path(report_baton,
439 svn_path_url_add_component2(
441 ths->repos_relpath, iterpool),
445 ths->lock ? ths->lock->token : NULL,
448 SVN_ERR(reporter->set_path(report_baton,
453 ths->lock ? ths->lock->token : NULL,
457 /* Possibly report a disjoint URL ... */
458 else if (this_switched)
459 SVN_ERR(reporter->link_path(report_baton,
461 svn_path_url_add_component2(
463 ths->repos_relpath, iterpool),
467 ths->lock ? ths->lock->token : NULL,
469 /* ... or perhaps just a differing revision or lock token,
470 or the mere presence of the file in a depth-empty dir. */
471 else if (ths->revnum != dir_rev
473 || dir_depth == svn_depth_empty)
474 SVN_ERR(reporter->set_path(report_baton,
479 ths->lock ? ths->lock->token : NULL,
481 } /* end file case */
483 /*** Directories (in recursive mode) ***/
484 else if (ths->kind == svn_node_dir
485 && (depth > svn_depth_files
486 || depth == svn_depth_unknown))
488 svn_boolean_t is_incomplete;
489 svn_boolean_t start_empty;
490 svn_depth_t report_depth = ths->depth;
492 is_incomplete = (ths->status == svn_wc__db_status_incomplete);
493 start_empty = is_incomplete;
495 if (!SVN_DEPTH_IS_RECURSIVE(depth))
496 report_depth = svn_depth_empty;
498 /* When a <= 1.6 working copy is upgraded without some of its
499 subdirectories we miss some information in the database. If we
500 report the revision as -1, the update editor will receive an
501 add_directory() while it still knows the directory.
503 This would raise strange tree conflicts and probably assertions
504 as it would a BASE vs BASE conflict */
505 if (is_incomplete && !SVN_IS_VALID_REVNUM(ths->revnum))
506 ths->revnum = dir_rev;
508 if (depth_compatibility_trick
509 && ths->depth <= svn_depth_files
510 && depth > ths->depth)
515 if (report_everything)
517 /* Report the dir unconditionally, one way or another... */
519 SVN_ERR(reporter->link_path(report_baton,
521 svn_path_url_add_component2(
523 ths->repos_relpath, iterpool),
527 ths->lock ? ths->lock->token
531 SVN_ERR(reporter->set_path(report_baton,
536 ths->lock ? ths->lock->token : NULL,
539 else if (this_switched)
541 /* ...or possibly report a disjoint URL ... */
542 SVN_ERR(reporter->link_path(report_baton,
544 svn_path_url_add_component2(
546 ths->repos_relpath, iterpool),
550 ths->lock ? ths->lock->token : NULL,
553 else if (ths->revnum != dir_rev
556 || dir_depth == svn_depth_empty
557 || dir_depth == svn_depth_files
558 || (dir_depth == svn_depth_immediates
559 && ths->depth != svn_depth_empty)
560 || (ths->depth < svn_depth_infinity
561 && SVN_DEPTH_IS_RECURSIVE(depth)))
563 /* ... or perhaps just a differing revision, lock token,
564 incomplete subdir, the mere presence of the directory
565 in a depth-empty or depth-files dir, or if the parent
566 dir is at depth-immediates but the child is not at
567 depth-empty. Also describe shallow subdirs if we are
568 trying to set depth to infinity. */
569 SVN_ERR(reporter->set_path(report_baton,
574 ths->lock ? ths->lock->token : NULL,
578 /* Finally, recurse if necessary and appropriate. */
579 if (SVN_DEPTH_IS_RECURSIVE(depth))
581 const char *repos_relpath = ths->repos_relpath;
583 if (repos_relpath == NULL)
585 repos_relpath = svn_relpath_join(dir_repos_relpath, child,
589 SVN_ERR(report_revisions_and_depths(db,
596 reporter, report_baton,
597 restore_files, depth,
599 depth_compatibility_trick,
602 cancel_func, cancel_baton,
603 notify_func, notify_baton,
606 } /* end directory case */
607 } /* end main entries loop */
609 /* We're done examining this dir's entries, so free everything. */
610 svn_pool_destroy(iterpool);
616 /*------------------------------------------------------------------*/
617 /*** Public Interfaces ***/
621 svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx,
622 const char *local_abspath,
623 const svn_ra_reporter3_t *reporter,
625 svn_boolean_t restore_files,
627 svn_boolean_t honor_depth_exclude,
628 svn_boolean_t depth_compatibility_trick,
629 svn_boolean_t use_commit_times,
630 svn_cancel_func_t cancel_func,
632 svn_wc_notify_func2_t notify_func,
634 apr_pool_t *scratch_pool)
636 svn_wc__db_t *db = wc_ctx->db;
637 svn_error_t *fserr, *err;
638 svn_revnum_t target_rev = SVN_INVALID_REVNUM;
639 svn_boolean_t start_empty;
640 svn_wc__db_status_t status;
641 svn_node_kind_t target_kind;
642 const char *repos_relpath, *repos_root_url;
643 svn_depth_t target_depth;
644 svn_wc__db_lock_t *target_lock;
645 svn_node_kind_t disk_kind;
646 svn_depth_t report_depth;
647 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
649 /* Get the base rev, which is the first revnum that entries will be
650 compared to, and some other WC info about the target. */
651 err = svn_wc__db_base_get_info(&status, &target_kind, &target_rev,
652 &repos_relpath, &repos_root_url,
653 NULL, NULL, NULL, NULL, &target_depth,
654 NULL, NULL, &target_lock,
656 db, local_abspath, scratch_pool,
660 || (status != svn_wc__db_status_normal
661 && status != svn_wc__db_status_incomplete))
663 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
664 return svn_error_trace(err);
666 svn_error_clear(err);
668 /* We don't know about this node, so all we have to do is tell
669 the reporter that we don't know this node.
671 But first we have to start the report by sending some basic
672 information for the root. */
674 if (depth == svn_depth_unknown)
675 depth = svn_depth_infinity;
677 SVN_ERR(reporter->set_path(report_baton, "", 0, depth, FALSE,
678 NULL, scratch_pool));
679 SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
681 /* Finish the report, which causes the update editor to be
683 SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
688 if (target_depth == svn_depth_unknown)
689 target_depth = svn_depth_infinity;
691 start_empty = (status == svn_wc__db_status_incomplete);
692 if (depth_compatibility_trick
693 && target_depth <= svn_depth_immediates
694 && depth > target_depth)
700 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
702 disk_kind = svn_node_unknown;
704 /* Determine if there is a missing node that should be restored */
706 && disk_kind == svn_node_none)
708 svn_wc__db_status_t wrk_status;
709 svn_node_kind_t wrk_kind;
710 const svn_checksum_t *checksum;
712 err = svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL, NULL,
713 NULL, NULL, NULL, NULL, NULL, &checksum, NULL,
714 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
715 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
718 scratch_pool, scratch_pool);
721 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
723 svn_error_clear(err);
724 wrk_status = svn_wc__db_status_not_present;
725 wrk_kind = svn_node_file;
730 if ((wrk_status == svn_wc__db_status_normal
731 || wrk_status == svn_wc__db_status_added
732 || wrk_status == svn_wc__db_status_incomplete)
733 && (wrk_kind == svn_node_dir || checksum))
735 SVN_ERR(restore_node(wc_ctx->db, local_abspath,
736 wrk_kind, use_commit_times,
737 notify_func, notify_baton,
743 report_depth = target_depth;
745 if (honor_depth_exclude
746 && depth != svn_depth_unknown
747 && depth < target_depth)
748 report_depth = depth;
750 /* The first call to the reporter merely informs it that the
751 top-level directory being updated is at BASE_REV. Its PATH
752 argument is ignored. */
753 SVN_ERR(reporter->set_path(report_baton, "", target_rev, report_depth,
754 start_empty, NULL, scratch_pool));
756 if (target_kind == svn_node_dir)
758 if (depth != svn_depth_empty)
760 /* Recursively crawl ROOT_DIRECTORY and report differing
762 err = report_revisions_and_depths(wc_ctx->db,
769 reporter, report_baton,
770 restore_files, depth,
772 depth_compatibility_trick,
775 cancel_func, cancel_baton,
776 notify_func, notify_baton,
783 else if (target_kind == svn_node_file || target_kind == svn_node_symlink)
785 const char *parent_abspath, *base;
786 svn_wc__db_status_t parent_status;
787 const char *parent_repos_relpath;
789 svn_dirent_split(&parent_abspath, &base, local_abspath,
792 /* We can assume a file is in the same repository as its parent
793 directory, so we only look at the relpath. */
794 err = svn_wc__db_base_get_info(&parent_status, NULL, NULL,
795 &parent_repos_relpath, NULL, NULL, NULL,
796 NULL, NULL, NULL, NULL, NULL, NULL,
799 scratch_pool, scratch_pool);
804 if (strcmp(repos_relpath,
805 svn_relpath_join(parent_repos_relpath, base,
808 /* This file is disjoint with respect to its parent
809 directory. Since we are looking at the actual target of
810 the report (not some file in a subdirectory of a target
811 directory), and that target is a file, we need to pass an
812 empty string to link_path. */
813 err = reporter->link_path(report_baton,
815 svn_path_url_add_component2(
822 target_lock ? target_lock->token : NULL,
827 else if (target_lock)
829 /* If this entry is a file node, we just want to report that
830 node's revision. Since we are looking at the actual target
831 of the report (not some file in a subdirectory of a target
832 directory), and that target is a file, we need to pass an
833 empty string to set_path. */
834 err = reporter->set_path(report_baton, "", target_rev,
837 target_lock ? target_lock->token : NULL,
844 /* Finish the report, which causes the update editor to be driven. */
845 return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
848 /* Clean up the fs transaction. */
849 if ((fserr = reporter->abort_report(report_baton, scratch_pool)))
851 fserr = svn_error_quick_wrap(fserr, _("Error aborting report"));
852 svn_error_compose(err, fserr);
854 return svn_error_trace(err);
857 /*** Copying stream ***/
859 /* A copying stream is a bit like the unix tee utility:
861 * It reads the SOURCE when asked for data and while returning it,
862 * also writes the same data to TARGET.
864 struct copying_stream_baton
866 /* Stream to read input from. */
867 svn_stream_t *source;
869 /* Stream to write all data read to. */
870 svn_stream_t *target;
876 read_handler_copy(void *baton, char *buffer, apr_size_t *len)
878 struct copying_stream_baton *btn = baton;
880 SVN_ERR(svn_stream_read(btn->source, buffer, len));
882 return svn_stream_write(btn->target, buffer, len);
887 close_handler_copy(void *baton)
889 struct copying_stream_baton *btn = baton;
891 SVN_ERR(svn_stream_close(btn->target));
892 return svn_stream_close(btn->source);
896 /* Return a stream - allocated in POOL - which reads its input
897 * from SOURCE and, while returning that to the caller, at the
898 * same time writes that to TARGET.
900 static svn_stream_t *
901 copying_stream(svn_stream_t *source,
902 svn_stream_t *target,
905 struct copying_stream_baton *baton;
906 svn_stream_t *stream;
908 baton = apr_palloc(pool, sizeof (*baton));
909 baton->source = source;
910 baton->target = target;
912 stream = svn_stream_create(baton, pool);
913 svn_stream_set_read(stream, read_handler_copy);
914 svn_stream_set_close(stream, close_handler_copy);
920 /* Set *STREAM to a stream from which the caller can read the pristine text
921 * of the working version of the file at LOCAL_ABSPATH. If the working
922 * version of LOCAL_ABSPATH has no pristine text because it is locally
923 * added, set *STREAM to an empty stream. If the working version of
924 * LOCAL_ABSPATH is not a file, return an error.
926 * Set *EXPECTED_MD5_CHECKSUM to the recorded MD5 checksum.
928 * Arrange for the actual checksum of the text to be calculated and written
929 * into *ACTUAL_MD5_CHECKSUM when the stream is read.
932 read_and_checksum_pristine_text(svn_stream_t **stream,
933 const svn_checksum_t **expected_md5_checksum,
934 svn_checksum_t **actual_md5_checksum,
936 const char *local_abspath,
937 apr_pool_t *result_pool,
938 apr_pool_t *scratch_pool)
940 svn_stream_t *base_stream;
942 SVN_ERR(svn_wc__get_pristine_contents(&base_stream, NULL, db, local_abspath,
943 result_pool, scratch_pool));
944 if (base_stream == NULL)
946 base_stream = svn_stream_empty(result_pool);
947 *expected_md5_checksum = NULL;
948 *actual_md5_checksum = NULL;
952 const svn_checksum_t *expected_md5;
954 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
955 NULL, NULL, NULL, NULL, &expected_md5,
956 NULL, NULL, NULL, NULL, NULL, NULL,
957 NULL, NULL, NULL, NULL, NULL, NULL,
958 NULL, NULL, NULL, NULL,
960 result_pool, scratch_pool));
961 if (expected_md5 == NULL)
962 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
963 _("Pristine checksum for file '%s' is missing"),
964 svn_dirent_local_style(local_abspath,
966 if (expected_md5->kind != svn_checksum_md5)
967 SVN_ERR(svn_wc__db_pristine_get_md5(&expected_md5, db, local_abspath,
969 result_pool, scratch_pool));
970 *expected_md5_checksum = expected_md5;
972 /* Arrange to set ACTUAL_MD5_CHECKSUM to the MD5 of what is *actually*
973 found when the base stream is read. */
974 base_stream = svn_stream_checksummed2(base_stream, actual_md5_checksum,
975 NULL, svn_checksum_md5, TRUE,
979 *stream = base_stream;
985 svn_wc__internal_transmit_text_deltas(const char **tempfile,
986 const svn_checksum_t **new_text_base_md5_checksum,
987 const svn_checksum_t **new_text_base_sha1_checksum,
989 const char *local_abspath,
990 svn_boolean_t fulltext,
991 const svn_delta_editor_t *editor,
993 apr_pool_t *result_pool,
994 apr_pool_t *scratch_pool)
996 svn_txdelta_window_handler_t handler;
998 const svn_checksum_t *expected_md5_checksum; /* recorded MD5 of BASE_S. */
999 svn_checksum_t *verify_checksum; /* calc'd MD5 of BASE_STREAM */
1000 svn_checksum_t *local_md5_checksum; /* calc'd MD5 of LOCAL_STREAM */
1001 svn_checksum_t *local_sha1_checksum; /* calc'd SHA1 of LOCAL_STREAM */
1002 const char *new_pristine_tmp_abspath;
1004 svn_stream_t *base_stream; /* delta source */
1005 svn_stream_t *local_stream; /* delta target: LOCAL_ABSPATH transl. to NF */
1007 /* Translated input */
1008 SVN_ERR(svn_wc__internal_translated_stream(&local_stream, db,
1009 local_abspath, local_abspath,
1010 SVN_WC_TRANSLATE_TO_NF,
1011 scratch_pool, scratch_pool));
1013 /* If the caller wants a copy of the working file translated to
1014 * repository-normal form, make the copy by tee-ing the stream and set
1015 * *TEMPFILE to the path to it. This is only needed for the 1.6 API,
1016 * 1.7 doesn't set TEMPFILE. Even when using the 1.6 API this file
1017 * is not used by the functions that would have used it when using
1018 * the 1.6 code. It's possible that 3rd party users (if there are any)
1019 * might expect this file to be a text-base. */
1022 svn_stream_t *tempstream;
1024 /* It can't be the same location as in 1.6 because the admin directory
1025 no longer exists. */
1026 SVN_ERR(svn_stream_open_unique(&tempstream, tempfile,
1027 NULL, svn_io_file_del_none,
1028 result_pool, scratch_pool));
1030 /* Wrap the translated stream with a new stream that writes the
1031 translated contents into the new text base file as we read from it.
1032 Note that the new text base file will be closed when the new stream
1034 local_stream = copying_stream(local_stream, tempstream, scratch_pool);
1036 if (new_text_base_sha1_checksum)
1038 svn_stream_t *new_pristine_stream;
1040 SVN_ERR(svn_wc__open_writable_base(&new_pristine_stream,
1041 &new_pristine_tmp_abspath,
1042 NULL, &local_sha1_checksum,
1044 scratch_pool, scratch_pool));
1045 local_stream = copying_stream(local_stream, new_pristine_stream,
1049 /* If sending a full text is requested, or if there is no pristine text
1050 * (e.g. the node is locally added), then set BASE_STREAM to an empty
1051 * stream and leave EXPECTED_MD5_CHECKSUM and VERIFY_CHECKSUM as NULL.
1053 * Otherwise, set BASE_STREAM to a stream providing the base (source) text
1054 * for the delta, set EXPECTED_MD5_CHECKSUM to its stored MD5 checksum,
1055 * and arrange for its VERIFY_CHECKSUM to be calculated later. */
1058 /* We will be computing a delta against the pristine contents */
1059 /* We need the expected checksum to be an MD-5 checksum rather than a
1060 * SHA-1 because we want to pass it to apply_textdelta(). */
1061 SVN_ERR(read_and_checksum_pristine_text(&base_stream,
1062 &expected_md5_checksum,
1065 scratch_pool, scratch_pool));
1069 /* Send a fulltext. */
1070 base_stream = svn_stream_empty(scratch_pool);
1071 expected_md5_checksum = NULL;
1072 verify_checksum = NULL;
1075 /* Tell the editor that we're about to apply a textdelta to the
1076 file baton; the editor returns to us a window consumer and baton. */
1078 /* apply_textdelta() is working against a base with this checksum */
1079 const char *base_digest_hex = NULL;
1081 if (expected_md5_checksum)
1082 /* ### Why '..._display()'? expected_md5_checksum should never be all-
1083 * zero, but if it is, we would want to pass NULL not an all-zero
1084 * digest to apply_textdelta(), wouldn't we? */
1085 base_digest_hex = svn_checksum_to_cstring_display(expected_md5_checksum,
1088 SVN_ERR(editor->apply_textdelta(file_baton, base_digest_hex, scratch_pool,
1089 &handler, &wh_baton));
1092 /* Run diff processing, throwing windows at the handler. */
1093 err = svn_txdelta_run(base_stream, local_stream,
1095 svn_checksum_md5, &local_md5_checksum,
1097 scratch_pool, scratch_pool);
1099 /* Close the two streams to force writing the digest */
1100 err = svn_error_compose_create(err, svn_stream_close(base_stream));
1101 err = svn_error_compose_create(err, svn_stream_close(local_stream));
1103 /* If we have an error, it may be caused by a corrupt text base,
1104 so check the checksum. */
1105 if (expected_md5_checksum && verify_checksum
1106 && !svn_checksum_match(expected_md5_checksum, verify_checksum))
1108 /* The entry checksum does not match the actual text
1109 base checksum. Extreme badness. Of course,
1110 theoretically we could just switch to
1111 fulltext transmission here, and everything would
1112 work fine; after all, we're going to replace the
1113 text base with a new one in a moment anyway, and
1114 we'd fix the checksum then. But it's better to
1115 error out. People should know that their text
1116 bases are getting corrupted, so they can
1117 investigate. Other commands could be affected,
1118 too, such as `svn diff'. */
1121 err = svn_error_compose_create(
1123 svn_io_remove_file2(*tempfile, TRUE, scratch_pool));
1125 err = svn_error_compose_create(
1126 svn_checksum_mismatch_err(expected_md5_checksum, verify_checksum,
1128 _("Checksum mismatch for text base of '%s'"),
1129 svn_dirent_local_style(local_abspath,
1133 return svn_error_create(SVN_ERR_WC_CORRUPT_TEXT_BASE, err, NULL);
1136 /* Now, handle that delta transmission error if any, so we can stop
1137 thinking about it after this point. */
1138 SVN_ERR_W(err, apr_psprintf(scratch_pool,
1139 _("While preparing '%s' for commit"),
1140 svn_dirent_local_style(local_abspath,
1143 if (new_text_base_md5_checksum)
1144 *new_text_base_md5_checksum = svn_checksum_dup(local_md5_checksum,
1146 if (new_text_base_sha1_checksum)
1148 SVN_ERR(svn_wc__db_pristine_install(db, new_pristine_tmp_abspath,
1149 local_sha1_checksum,
1152 *new_text_base_sha1_checksum = svn_checksum_dup(local_sha1_checksum,
1156 /* Close the file baton, and get outta here. */
1157 return svn_error_trace(
1158 editor->close_file(file_baton,
1159 svn_checksum_to_cstring(local_md5_checksum,
1165 svn_wc_transmit_text_deltas3(const svn_checksum_t **new_text_base_md5_checksum,
1166 const svn_checksum_t **new_text_base_sha1_checksum,
1167 svn_wc_context_t *wc_ctx,
1168 const char *local_abspath,
1169 svn_boolean_t fulltext,
1170 const svn_delta_editor_t *editor,
1172 apr_pool_t *result_pool,
1173 apr_pool_t *scratch_pool)
1175 return svn_wc__internal_transmit_text_deltas(NULL,
1176 new_text_base_md5_checksum,
1177 new_text_base_sha1_checksum,
1178 wc_ctx->db, local_abspath,
1180 file_baton, result_pool,
1185 svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db,
1186 const char *local_abspath,
1187 const svn_delta_editor_t *editor,
1189 apr_pool_t *scratch_pool)
1191 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1193 apr_array_header_t *propmods;
1194 svn_node_kind_t kind;
1196 SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath,
1197 FALSE /* allow_missing */,
1198 FALSE /* show_deleted */,
1199 FALSE /* show_hidden */,
1202 if (kind == svn_node_none)
1203 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1204 _("The node '%s' was not found."),
1205 svn_dirent_local_style(local_abspath, iterpool));
1207 /* Get an array of local changes by comparing the hashes. */
1208 SVN_ERR(svn_wc__internal_propdiff(&propmods, NULL, db, local_abspath,
1209 scratch_pool, iterpool));
1211 /* Apply each local change to the baton */
1212 for (i = 0; i < propmods->nelts; i++)
1214 const svn_prop_t *p = &APR_ARRAY_IDX(propmods, i, svn_prop_t);
1216 svn_pool_clear(iterpool);
1218 if (kind == svn_node_file)
1219 SVN_ERR(editor->change_file_prop(baton, p->name, p->value,
1222 SVN_ERR(editor->change_dir_prop(baton, p->name, p->value,
1226 svn_pool_destroy(iterpool);
1227 return SVN_NO_ERROR;
1231 svn_wc_transmit_prop_deltas2(svn_wc_context_t *wc_ctx,
1232 const char *local_abspath,
1233 const svn_delta_editor_t *editor,
1235 apr_pool_t *scratch_pool)
1237 return svn_wc__internal_transmit_prop_deltas(wc_ctx->db, local_abspath,
1238 editor, baton, scratch_pool);