1 /*
2 * adm_crawler.c: report local WC mods to an Editor.
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24 /* ==================================================================== */
25
26
27 #include <string.h>
28
29 #include <apr_pools.h>
30 #include <apr_file_io.h>
31 #include <apr_hash.h>
32
33 #include "svn_hash.h"
34 #include "svn_types.h"
35 #include "svn_pools.h"
36 #include "svn_wc.h"
37 #include "svn_io.h"
38 #include "svn_delta.h"
39 #include "svn_dirent_uri.h"
40 #include "svn_path.h"
41
42 #include "private/svn_wc_private.h"
43
44 #include "wc.h"
45 #include "adm_files.h"
46 #include "translate.h"
47 #include "workqueue.h"
48 #include "conflicts.h"
49
50 #include "svn_private_config.h"
51
52
53 /* Helper for report_revisions_and_depths().
54
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.
61
62 If MARK_RESOLVED_TEXT_CONFLICT is TRUE, mark as resolved any existing
63 text conflict on LOCAL_ABSPATH.
64
65 Not that a valid access baton with a write lock to the directory of
66 LOCAL_ABSPATH must be available in DB.*/
67 static svn_error_t *
restore_file(svn_wc__db_t * db,const char * local_abspath,svn_boolean_t use_commit_times,svn_boolean_t mark_resolved_text_conflict,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)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,
73 void *cancel_baton,
74 apr_pool_t *scratch_pool)
75 {
76 svn_skel_t *work_item;
77
78 SVN_ERR(svn_wc__wq_build_file_install(&work_item,
79 db, local_abspath,
80 NULL /* source_abspath */,
81 use_commit_times,
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));
88
89 /* Run the work item immediately. */
90 SVN_ERR(svn_wc__wq_run(db, local_abspath,
91 cancel_func, cancel_baton,
92 scratch_pool));
93
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,
98 scratch_pool));
99
100 return SVN_NO_ERROR;
101 }
102
103 svn_error_t *
svn_wc_restore(svn_wc_context_t * wc_ctx,const char * local_abspath,svn_boolean_t use_commit_times,apr_pool_t * scratch_pool)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)
108 {
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;
114
115 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
116
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,
121 scratch_pool));
122
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));
129
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)*/)))
136 {
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,
140 scratch_pool));
141 }
142
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 */,
147 scratch_pool));
148 else
149 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
150
151 return SVN_NO_ERROR;
152 }
153
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
157 last-commit-time.
158
159 This function does all temporary allocations in SCRATCH_POOL
160 */
161 static svn_error_t *
restore_node(svn_wc__db_t * db,const char * local_abspath,svn_node_kind_t kind,svn_boolean_t mark_resolved_text_conflict,svn_boolean_t use_commit_times,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * 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,
168 void *cancel_baton,
169 svn_wc_notify_func2_t notify_func,
170 void *notify_baton,
171 apr_pool_t *scratch_pool)
172 {
173 if (kind == svn_node_file || kind == svn_node_symlink)
174 {
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,
179 scratch_pool));
180 }
181 else if (kind == svn_node_dir)
182 {
183 /* Recreating a directory is just a mkdir */
184 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
185 }
186
187 /* ... report the restoration to the caller. */
188 if (notify_func != NULL)
189 {
190 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
191 svn_wc_notify_restore,
192 scratch_pool);
193 notify->kind = svn_node_file;
194 (*notify_func)(notify_baton, notify, scratch_pool);
195 }
196
197 return SVN_NO_ERROR;
198 }
199
200 /* The recursive crawler that describes a mixed-revision working
201 copy to an RA layer. Used to initiate updates.
202
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.
208
209 Report DIR_ABSPATH to the reporter as REPORT_RELPATH.
210
211 Alternatively, if REPORT_EVERYTHING is set, then report all
212 children unconditionally.
213
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:
219
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.)
231
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.
235
236 DEPTH_COMPATIBILITY_TRICK means the same thing here as it does
237 in svn_wc_crawl_revisions5().
238
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. */
243 static svn_error_t *
report_revisions_and_depths(svn_wc__db_t * db,const char * dir_abspath,const char * report_relpath,svn_revnum_t dir_rev,const char * dir_repos_relpath,const char * dir_repos_root,svn_depth_t dir_depth,const svn_ra_reporter3_t * reporter,void * report_baton,svn_boolean_t restore_files,svn_depth_t depth,svn_boolean_t honor_depth_exclude,svn_boolean_t depth_compatibility_trick,svn_boolean_t report_everything,svn_boolean_t use_commit_times,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)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,
252 void *report_baton,
253 svn_boolean_t restore_files,
254 svn_depth_t depth,
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,
260 void *cancel_baton,
261 svn_wc_notify_func2_t notify_func,
262 void *notify_baton,
263 apr_pool_t *scratch_pool)
264 {
265 apr_hash_t *base_children;
266 apr_hash_t *dirents;
267 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
268 apr_hash_index_t *hi;
269 svn_error_t *err;
270
271
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
274 hides children). */
275 SVN_ERR(svn_wc__db_base_get_children_info(&base_children, db, dir_abspath,
276 scratch_pool, iterpool));
277
278 if (restore_files)
279 {
280 err = svn_io_get_dirents3(&dirents, dir_abspath, TRUE,
281 scratch_pool, scratch_pool);
282
283 if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
284 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
285 {
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
289 directory */
290 restore_files = FALSE;
291 dirents = NULL;
292 }
293 else
294 SVN_ERR(err);
295 }
296 else
297 dirents = NULL;
298
299 /*** Do the real reporting and recursing. ***/
300
301 /* Looping over current directory's BASE children: */
302 for (hi = apr_hash_first(scratch_pool, base_children);
303 hi != NULL;
304 hi = apr_hash_next(hi))
305 {
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);
311
312 if (cancel_func)
313 SVN_ERR(cancel_func(cancel_baton));
314
315 /* Clear the iteration subpool here because the loop has a bunch
316 of 'continue' jump statements. */
317 svn_pool_clear(iterpool);
318
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);
322
323 /*** File Externals **/
324 if (ths->update_root)
325 {
326 /* File externals are ... special. We ignore them. */;
327 continue;
328 }
329
330 /* First check for exclusion */
331 if (ths->status == svn_wc__db_status_excluded)
332 {
333 if (honor_depth_exclude)
334 {
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. */
340
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,
345 this_report_relpath,
346 dir_rev,
347 svn_depth_exclude,
348 FALSE,
349 NULL,
350 iterpool));
351 }
352 else
353 {
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));
359 }
360 continue;
361 }
362
363 /*** The Big Tests: ***/
364 if (ths->status == svn_wc__db_status_server_excluded
365 || ths->status == svn_wc__db_status_not_present)
366 {
367 /* If the entry is 'absent' or 'not-present', make sure the server
368 knows it's gone...
369 ...unless we're reporting everything, in which case we're
370 going to report it missing later anyway.
371
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,
377 iterpool));
378 continue;
379 }
380
381 /* Is the entry NOT on the disk? We may be able to restore it. */
382 if (restore_files
383 && svn_hash_gets(dirents, child) == NULL)
384 {
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;
389
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));
396
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))
401 {
402 svn_node_kind_t dirent_kind;
403
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
407 version */
408 SVN_ERR(svn_io_check_path(this_abspath, &dirent_kind, iterpool));
409
410 if (dirent_kind == svn_node_none)
411 {
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));
416 }
417 }
418 }
419
420 /* And finally prepare for reporting */
421 if (!ths->repos_relpath)
422 {
423 ths->repos_relpath = svn_relpath_join(dir_repos_relpath, child,
424 iterpool);
425 }
426 else
427 {
428 const char *childname
429 = svn_relpath_skip_ancestor(dir_repos_relpath, ths->repos_relpath);
430
431 if (childname == NULL || strcmp(childname, child) != 0)
432 {
433 this_switched = TRUE;
434 }
435 }
436
437 /* Tweak THIS_DEPTH to a useful value. */
438 if (ths->depth == svn_depth_unknown)
439 ths->depth = svn_depth_infinity;
440
441 /*** Files ***/
442 if (ths->kind == svn_node_file
443 || ths->kind == svn_node_symlink)
444 {
445 if (report_everything)
446 {
447 /* Report the file unconditionally, one way or another. */
448 if (this_switched)
449 SVN_ERR(reporter->link_path(report_baton,
450 this_report_relpath,
451 svn_path_url_add_component2(
452 dir_repos_root,
453 ths->repos_relpath, iterpool),
454 ths->revnum,
455 ths->depth,
456 FALSE,
457 ths->lock ? ths->lock->token : NULL,
458 iterpool));
459 else
460 SVN_ERR(reporter->set_path(report_baton,
461 this_report_relpath,
462 ths->revnum,
463 ths->depth,
464 FALSE,
465 ths->lock ? ths->lock->token : NULL,
466 iterpool));
467 }
468
469 /* Possibly report a disjoint URL ... */
470 else if (this_switched)
471 SVN_ERR(reporter->link_path(report_baton,
472 this_report_relpath,
473 svn_path_url_add_component2(
474 dir_repos_root,
475 ths->repos_relpath, iterpool),
476 ths->revnum,
477 ths->depth,
478 FALSE,
479 ths->lock ? ths->lock->token : NULL,
480 iterpool));
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
484 || ths->lock
485 || dir_depth == svn_depth_empty)
486 SVN_ERR(reporter->set_path(report_baton,
487 this_report_relpath,
488 ths->revnum,
489 ths->depth,
490 FALSE,
491 ths->lock ? ths->lock->token : NULL,
492 iterpool));
493 } /* end file case */
494
495 /*** Directories (in recursive mode) ***/
496 else if (ths->kind == svn_node_dir
497 && (depth > svn_depth_files
498 || depth == svn_depth_unknown))
499 {
500 svn_boolean_t is_incomplete;
501 svn_boolean_t start_empty;
502 svn_depth_t report_depth = ths->depth;
503
504 is_incomplete = (ths->status == svn_wc__db_status_incomplete);
505 start_empty = is_incomplete;
506
507 if (!SVN_DEPTH_IS_RECURSIVE(depth))
508 report_depth = svn_depth_empty;
509
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.
514
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;
519
520 if (depth_compatibility_trick
521 && ths->depth <= svn_depth_files
522 && depth > ths->depth)
523 {
524 start_empty = TRUE;
525 }
526
527 if (report_everything)
528 {
529 /* Report the dir unconditionally, one way or another... */
530 if (this_switched)
531 SVN_ERR(reporter->link_path(report_baton,
532 this_report_relpath,
533 svn_path_url_add_component2(
534 dir_repos_root,
535 ths->repos_relpath, iterpool),
536 ths->revnum,
537 report_depth,
538 start_empty,
539 ths->lock ? ths->lock->token
540 : NULL,
541 iterpool));
542 else
543 SVN_ERR(reporter->set_path(report_baton,
544 this_report_relpath,
545 ths->revnum,
546 report_depth,
547 start_empty,
548 ths->lock ? ths->lock->token : NULL,
549 iterpool));
550 }
551 else if (this_switched)
552 {
553 /* ...or possibly report a disjoint URL ... */
554 SVN_ERR(reporter->link_path(report_baton,
555 this_report_relpath,
556 svn_path_url_add_component2(
557 dir_repos_root,
558 ths->repos_relpath, iterpool),
559 ths->revnum,
560 report_depth,
561 start_empty,
562 ths->lock ? ths->lock->token : NULL,
563 iterpool));
564 }
565 else if (ths->revnum != dir_rev
566 || ths->lock
567 || is_incomplete
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)))
574 {
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,
582 this_report_relpath,
583 ths->revnum,
584 report_depth,
585 start_empty,
586 ths->lock ? ths->lock->token : NULL,
587 iterpool));
588 }
589
590 /* Finally, recurse if necessary and appropriate. */
591 if (SVN_DEPTH_IS_RECURSIVE(depth))
592 {
593 const char *repos_relpath = ths->repos_relpath;
594
595 if (repos_relpath == NULL)
596 {
597 repos_relpath = svn_relpath_join(dir_repos_relpath, child,
598 iterpool);
599 }
600
601 SVN_ERR(report_revisions_and_depths(db,
602 this_abspath,
603 this_report_relpath,
604 ths->revnum,
605 repos_relpath,
606 dir_repos_root,
607 ths->depth,
608 reporter, report_baton,
609 restore_files, depth,
610 honor_depth_exclude,
611 depth_compatibility_trick,
612 start_empty,
613 use_commit_times,
614 cancel_func, cancel_baton,
615 notify_func, notify_baton,
616 iterpool));
617 }
618 } /* end directory case */
619 } /* end main entries loop */
620
621 /* We're done examining this dir's entries, so free everything. */
622 svn_pool_destroy(iterpool);
623
624 return SVN_NO_ERROR;
625 }
626
627
628 /*------------------------------------------------------------------*/
629 /*** Public Interfaces ***/
630
631
632 svn_error_t *
svn_wc_crawl_revisions5(svn_wc_context_t * wc_ctx,const char * local_abspath,const svn_ra_reporter3_t * reporter,void * report_baton,svn_boolean_t restore_files,svn_depth_t depth,svn_boolean_t honor_depth_exclude,svn_boolean_t depth_compatibility_trick,svn_boolean_t use_commit_times,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)633 svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx,
634 const char *local_abspath,
635 const svn_ra_reporter3_t *reporter,
636 void *report_baton,
637 svn_boolean_t restore_files,
638 svn_depth_t depth,
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,
643 void *cancel_baton,
644 svn_wc_notify_func2_t notify_func,
645 void *notify_baton,
646 apr_pool_t *scratch_pool)
647 {
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));
660
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,
667 NULL, NULL, NULL,
668 db, local_abspath, scratch_pool,
669 scratch_pool);
670
671 if (err
672 || (status != svn_wc__db_status_normal
673 && status != svn_wc__db_status_incomplete))
674 {
675 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
676 return svn_error_trace(err);
677
678 svn_error_clear(err);
679
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.
682
683 But first we have to start the report by sending some basic
684 information for the root. */
685
686 if (depth == svn_depth_unknown)
687 depth = svn_depth_infinity;
688
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));
692
693 /* Finish the report, which causes the update editor to be
694 driven. */
695 SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
696
697 return SVN_NO_ERROR;
698 }
699
700 if (target_depth == svn_depth_unknown)
701 target_depth = svn_depth_infinity;
702
703 start_empty = (status == svn_wc__db_status_incomplete);
704 if (depth_compatibility_trick
705 && target_depth <= svn_depth_immediates
706 && depth > target_depth)
707 {
708 start_empty = TRUE;
709 }
710
711 if (restore_files)
712 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
713 else
714 disk_kind = svn_node_unknown;
715
716 /* Determine if there is a missing node that should be restored */
717 if (restore_files
718 && disk_kind == svn_node_none)
719 {
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;
724
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,
729 NULL, NULL,
730 db, local_abspath,
731 scratch_pool, scratch_pool);
732
733
734 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
735 {
736 svn_error_clear(err);
737 wrk_status = svn_wc__db_status_not_present;
738 wrk_kind = svn_node_file;
739 }
740 else
741 SVN_ERR(err);
742
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))
747 {
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,
752 scratch_pool));
753 }
754 }
755
756 {
757 report_depth = target_depth;
758
759 if (honor_depth_exclude
760 && depth != svn_depth_unknown
761 && depth < target_depth)
762 report_depth = depth;
763
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));
769 }
770 if (target_kind == svn_node_dir)
771 {
772 if (depth != svn_depth_empty)
773 {
774 /* Recursively crawl ROOT_DIRECTORY and report differing
775 revisions. */
776 err = report_revisions_and_depths(wc_ctx->db,
777 local_abspath,
778 "",
779 target_rev,
780 repos_relpath,
781 repos_root_url,
782 report_depth,
783 reporter, report_baton,
784 restore_files, depth,
785 honor_depth_exclude,
786 depth_compatibility_trick,
787 start_empty,
788 use_commit_times,
789 cancel_func, cancel_baton,
790 notify_func, notify_baton,
791 scratch_pool);
792 if (err)
793 goto abort_report;
794 }
795 }
796
797 else if (target_kind == svn_node_file || target_kind == svn_node_symlink)
798 {
799 const char *parent_abspath, *base;
800 svn_wc__db_status_t parent_status;
801 const char *parent_repos_relpath;
802
803 svn_dirent_split(&parent_abspath, &base, local_abspath,
804 scratch_pool);
805
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,
811 NULL, NULL, NULL,
812 db, parent_abspath,
813 scratch_pool, scratch_pool);
814
815 if (err)
816 goto abort_report;
817
818 if (strcmp(repos_relpath,
819 svn_relpath_join(parent_repos_relpath, base,
820 scratch_pool)) != 0)
821 {
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,
828 "",
829 svn_path_url_add_component2(
830 repos_root_url,
831 repos_relpath,
832 scratch_pool),
833 target_rev,
834 svn_depth_infinity,
835 FALSE,
836 target_lock ? target_lock->token : NULL,
837 scratch_pool);
838 if (err)
839 goto abort_report;
840 }
841 else if (target_lock)
842 {
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,
849 svn_depth_infinity,
850 FALSE,
851 target_lock ? target_lock->token : NULL,
852 scratch_pool);
853 if (err)
854 goto abort_report;
855 }
856 }
857
858 /* Finish the report, which causes the update editor to be driven. */
859 return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
860
861 abort_report:
862 /* Clean up the fs transaction. */
863 if ((fserr = reporter->abort_report(report_baton, scratch_pool)))
864 {
865 fserr = svn_error_quick_wrap(fserr, _("Error aborting report"));
866 svn_error_compose(err, fserr);
867 }
868 return svn_error_trace(err);
869 }
870
871 /*** Copying stream ***/
872
873 /* A copying stream is a bit like the unix tee utility:
874 *
875 * It reads the SOURCE when asked for data and while returning it,
876 * also writes the same data to TARGET.
877 */
878 struct copying_stream_baton
879 {
880 /* Stream to read input from. */
881 svn_stream_t *source;
882
883 /* Stream to write all data read to. */
884 svn_stream_t *target;
885 };
886
887
888 /* */
889 static svn_error_t *
read_handler_copy(void * baton,char * buffer,apr_size_t * len)890 read_handler_copy(void *baton, char *buffer, apr_size_t *len)
891 {
892 struct copying_stream_baton *btn = baton;
893
894 SVN_ERR(svn_stream_read_full(btn->source, buffer, len));
895
896 return svn_stream_write(btn->target, buffer, len);
897 }
898
899 /* */
900 static svn_error_t *
close_handler_copy(void * baton)901 close_handler_copy(void *baton)
902 {
903 struct copying_stream_baton *btn = baton;
904
905 SVN_ERR(svn_stream_close(btn->target));
906 return svn_stream_close(btn->source);
907 }
908
909
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.
913 */
914 static svn_stream_t *
copying_stream(svn_stream_t * source,svn_stream_t * target,apr_pool_t * pool)915 copying_stream(svn_stream_t *source,
916 svn_stream_t *target,
917 apr_pool_t *pool)
918 {
919 struct copying_stream_baton *baton;
920 svn_stream_t *stream;
921
922 baton = apr_palloc(pool, sizeof (*baton));
923 baton->source = source;
924 baton->target = target;
925
926 stream = svn_stream_create(baton, pool);
927 svn_stream_set_read2(stream, NULL /* only full read support */,
928 read_handler_copy);
929 svn_stream_set_close(stream, close_handler_copy);
930
931 return stream;
932 }
933
934
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.
940 *
941 * Set *EXPECTED_MD5_CHECKSUM to the recorded MD5 checksum.
942 *
943 * Arrange for the actual checksum of the text to be calculated and written
944 * into *ACTUAL_MD5_CHECKSUM when the stream is read.
945 */
946 static svn_error_t *
read_and_checksum_pristine_text(svn_stream_t ** stream,const svn_checksum_t ** expected_md5_checksum,svn_checksum_t ** actual_md5_checksum,svn_wc__db_t * db,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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,
950 svn_wc__db_t *db,
951 const char *local_abspath,
952 apr_pool_t *result_pool,
953 apr_pool_t *scratch_pool)
954 {
955 svn_stream_t *base_stream;
956
957 SVN_ERR(svn_wc__get_pristine_contents(&base_stream, NULL, db, local_abspath,
958 result_pool, scratch_pool));
959 if (base_stream == NULL)
960 {
961 base_stream = svn_stream_empty(result_pool);
962 *expected_md5_checksum = NULL;
963 *actual_md5_checksum = NULL;
964 }
965 else
966 {
967 const svn_checksum_t *expected_md5;
968
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,
974 db, local_abspath,
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,
980 scratch_pool));
981 if (expected_md5->kind != svn_checksum_md5)
982 SVN_ERR(svn_wc__db_pristine_get_md5(&expected_md5, db, local_abspath,
983 expected_md5,
984 result_pool, scratch_pool));
985 *expected_md5_checksum = expected_md5;
986
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,
991 result_pool);
992 }
993
994 *stream = base_stream;
995 return SVN_NO_ERROR;
996 }
997
998
999 svn_error_t *
svn_wc__internal_transmit_text_deltas(const char ** tempfile,const svn_checksum_t ** new_text_base_md5_checksum,const svn_checksum_t ** new_text_base_sha1_checksum,svn_wc__db_t * db,const char * local_abspath,svn_boolean_t fulltext,const svn_delta_editor_t * editor,void * file_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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,
1003 svn_wc__db_t *db,
1004 const char *local_abspath,
1005 svn_boolean_t fulltext,
1006 const svn_delta_editor_t *editor,
1007 void *file_baton,
1008 apr_pool_t *result_pool,
1009 apr_pool_t *scratch_pool)
1010 {
1011 svn_txdelta_window_handler_t handler;
1012 void *wh_baton;
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;
1018 svn_error_t *err;
1019 svn_error_t *err2;
1020 svn_stream_t *base_stream; /* delta source */
1021 svn_stream_t *local_stream; /* delta target: LOCAL_ABSPATH transl. to NF */
1022
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));
1028
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. */
1036 if (tempfile)
1037 {
1038 svn_stream_t *tempstream;
1039
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));
1045
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
1049 is closed. */
1050 local_stream = copying_stream(local_stream, tempstream, scratch_pool);
1051 }
1052 if (new_text_base_sha1_checksum)
1053 {
1054 svn_stream_t *new_pristine_stream;
1055
1056 SVN_ERR(svn_wc__db_pristine_prepare_install(&new_pristine_stream,
1057 &install_data,
1058 &local_sha1_checksum, NULL,
1059 db, local_abspath,
1060 scratch_pool, scratch_pool));
1061 local_stream = copying_stream(local_stream, new_pristine_stream,
1062 scratch_pool);
1063 }
1064
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.
1068 *
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. */
1072 if (! fulltext)
1073 {
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,
1079 &verify_checksum,
1080 db, local_abspath,
1081 scratch_pool, scratch_pool));
1082 }
1083 else
1084 {
1085 /* Send a fulltext. */
1086 base_stream = svn_stream_empty(scratch_pool);
1087 expected_md5_checksum = NULL;
1088 verify_checksum = NULL;
1089 }
1090
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. */
1093 {
1094 /* apply_textdelta() is working against a base with this checksum */
1095 const char *base_digest_hex = NULL;
1096
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,
1102 scratch_pool);
1103
1104 SVN_ERR(editor->apply_textdelta(file_baton, base_digest_hex, scratch_pool,
1105 &handler, &wh_baton));
1106 }
1107
1108 /* Run diff processing, throwing windows at the handler. */
1109 err = svn_txdelta_run(base_stream, local_stream,
1110 handler, wh_baton,
1111 svn_checksum_md5, &local_md5_checksum,
1112 NULL, NULL,
1113 scratch_pool, scratch_pool);
1114
1115 /* Close the two streams to force writing the digest */
1116 err2 = svn_stream_close(base_stream);
1117 if (err2)
1118 {
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);
1123 }
1124
1125 err = svn_error_compose_create(err, svn_stream_close(local_stream));
1126
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))
1131 {
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'. */
1143
1144 if (tempfile)
1145 err = svn_error_compose_create(
1146 err,
1147 svn_io_remove_file2(*tempfile, TRUE, scratch_pool));
1148
1149 err = svn_error_compose_create(
1150 svn_checksum_mismatch_err(expected_md5_checksum, verify_checksum,
1151 scratch_pool,
1152 _("Checksum mismatch for text base of '%s'"),
1153 svn_dirent_local_style(local_abspath,
1154 scratch_pool)),
1155 err);
1156
1157 return svn_error_create(SVN_ERR_WC_CORRUPT_TEXT_BASE, err, NULL);
1158 }
1159
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,
1165 scratch_pool)));
1166
1167 if (new_text_base_md5_checksum)
1168 *new_text_base_md5_checksum = svn_checksum_dup(local_md5_checksum,
1169 result_pool);
1170 if (new_text_base_sha1_checksum)
1171 {
1172 SVN_ERR(svn_wc__db_pristine_install(install_data,
1173 local_sha1_checksum,
1174 local_md5_checksum,
1175 scratch_pool));
1176 *new_text_base_sha1_checksum = svn_checksum_dup(local_sha1_checksum,
1177 result_pool);
1178 }
1179
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,
1184 scratch_pool),
1185 scratch_pool));
1186 }
1187
1188 svn_error_t *
svn_wc_transmit_text_deltas3(const svn_checksum_t ** new_text_base_md5_checksum,const svn_checksum_t ** new_text_base_sha1_checksum,svn_wc_context_t * wc_ctx,const char * local_abspath,svn_boolean_t fulltext,const svn_delta_editor_t * editor,void * file_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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,
1195 void *file_baton,
1196 apr_pool_t *result_pool,
1197 apr_pool_t *scratch_pool)
1198 {
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,
1203 fulltext, editor,
1204 file_baton, result_pool,
1205 scratch_pool);
1206 }
1207
1208 svn_error_t *
svn_wc__internal_transmit_prop_deltas(svn_wc__db_t * db,const char * local_abspath,const svn_delta_editor_t * editor,void * baton,apr_pool_t * scratch_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,
1212 void *baton,
1213 apr_pool_t *scratch_pool)
1214 {
1215 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1216 int i;
1217 apr_array_header_t *propmods;
1218 svn_node_kind_t kind;
1219
1220 SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath,
1221 FALSE /* allow_missing */,
1222 FALSE /* show_deleted */,
1223 FALSE /* show_hidden */,
1224 iterpool));
1225
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));
1230
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));
1234
1235 /* Apply each local change to the baton */
1236 for (i = 0; i < propmods->nelts; i++)
1237 {
1238 const svn_prop_t *p = &APR_ARRAY_IDX(propmods, i, svn_prop_t);
1239
1240 svn_pool_clear(iterpool);
1241
1242 if (kind == svn_node_file)
1243 SVN_ERR(editor->change_file_prop(baton, p->name, p->value,
1244 iterpool));
1245 else
1246 SVN_ERR(editor->change_dir_prop(baton, p->name, p->value,
1247 iterpool));
1248 }
1249
1250 svn_pool_destroy(iterpool);
1251 return SVN_NO_ERROR;
1252 }
1253
1254 svn_error_t *
svn_wc_transmit_prop_deltas2(svn_wc_context_t * wc_ctx,const char * local_abspath,const svn_delta_editor_t * editor,void * baton,apr_pool_t * scratch_pool)1255 svn_wc_transmit_prop_deltas2(svn_wc_context_t *wc_ctx,
1256 const char *local_abspath,
1257 const svn_delta_editor_t *editor,
1258 void *baton,
1259 apr_pool_t *scratch_pool)
1260 {
1261 return svn_wc__internal_transmit_prop_deltas(wc_ctx->db, local_abspath,
1262 editor, baton, scratch_pool);
1263 }
1264