]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_wc/adm_crawler.c
Merge bmake-20180512
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_wc / adm_crawler.c
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 *
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 *
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 *
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 *
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 \f
628 /*------------------------------------------------------------------*/
629 /*** Public Interfaces ***/
630
631
632 svn_error_t *
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 *
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 *
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 /* Implements svn_stream_seek_fn_t */
910 static svn_error_t *
911 seek_handler_copy(void *baton, const svn_stream_mark_t *mark)
912 {
913   struct copying_stream_baton *btn = baton;
914
915   /* Only reset support. */
916   if (mark)
917     {
918       return svn_error_create(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED,
919                               NULL, NULL);
920     }
921   else
922     {
923       SVN_ERR(svn_stream_reset(btn->source));
924       SVN_ERR(svn_stream_reset(btn->target));
925     }
926
927   return SVN_NO_ERROR;
928 }
929
930
931 /* Return a stream - allocated in POOL - which reads its input
932  * from SOURCE and, while returning that to the caller, at the
933  * same time writes that to TARGET.
934  */
935 static svn_stream_t *
936 copying_stream(svn_stream_t *source,
937                svn_stream_t *target,
938                apr_pool_t *pool)
939 {
940   struct copying_stream_baton *baton;
941   svn_stream_t *stream;
942
943   baton = apr_palloc(pool, sizeof (*baton));
944   baton->source = source;
945   baton->target = target;
946
947   stream = svn_stream_create(baton, pool);
948   svn_stream_set_read2(stream, NULL /* only full read support */,
949                        read_handler_copy);
950   svn_stream_set_close(stream, close_handler_copy);
951
952   if (svn_stream_supports_reset(source) && svn_stream_supports_reset(target))
953     {
954       svn_stream_set_seek(stream, seek_handler_copy);
955     }
956
957   return stream;
958 }
959
960
961 /* Set *STREAM to a stream from which the caller can read the pristine text
962  * of the working version of the file at LOCAL_ABSPATH.  If the working
963  * version of LOCAL_ABSPATH has no pristine text because it is locally
964  * added, set *STREAM to an empty stream.  If the working version of
965  * LOCAL_ABSPATH is not a file, return an error.
966  *
967  * Set *EXPECTED_MD5_CHECKSUM to the recorded MD5 checksum.
968  *
969  * Arrange for the actual checksum of the text to be calculated and written
970  * into *ACTUAL_MD5_CHECKSUM when the stream is read.
971  */
972 static svn_error_t *
973 read_and_checksum_pristine_text(svn_stream_t **stream,
974                                 const svn_checksum_t **expected_md5_checksum,
975                                 svn_checksum_t **actual_md5_checksum,
976                                 svn_wc__db_t *db,
977                                 const char *local_abspath,
978                                 apr_pool_t *result_pool,
979                                 apr_pool_t *scratch_pool)
980 {
981   svn_stream_t *base_stream;
982
983   SVN_ERR(svn_wc__get_pristine_contents(&base_stream, NULL, db, local_abspath,
984                                         result_pool, scratch_pool));
985   if (base_stream == NULL)
986     {
987       base_stream = svn_stream_empty(result_pool);
988       *expected_md5_checksum = NULL;
989       *actual_md5_checksum = NULL;
990     }
991   else
992     {
993       const svn_checksum_t *expected_md5;
994
995       SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
996                                    NULL, NULL, NULL, NULL, &expected_md5,
997                                    NULL, NULL, NULL, NULL, NULL, NULL,
998                                    NULL, NULL, NULL, NULL, NULL, NULL,
999                                    NULL, NULL, NULL, NULL,
1000                                    db, local_abspath,
1001                                    result_pool, scratch_pool));
1002       if (expected_md5 == NULL)
1003         return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
1004                                  _("Pristine checksum for file '%s' is missing"),
1005                                  svn_dirent_local_style(local_abspath,
1006                                                         scratch_pool));
1007       if (expected_md5->kind != svn_checksum_md5)
1008         SVN_ERR(svn_wc__db_pristine_get_md5(&expected_md5, db, local_abspath,
1009                                             expected_md5,
1010                                             result_pool, scratch_pool));
1011       *expected_md5_checksum = expected_md5;
1012
1013       /* Arrange to set ACTUAL_MD5_CHECKSUM to the MD5 of what is *actually*
1014          found when the base stream is read. */
1015       base_stream = svn_stream_checksummed2(base_stream, actual_md5_checksum,
1016                                             NULL, svn_checksum_md5, TRUE,
1017                                             result_pool);
1018     }
1019
1020   *stream = base_stream;
1021   return SVN_NO_ERROR;
1022 }
1023
1024 typedef struct open_txdelta_stream_baton_t
1025 {
1026   svn_boolean_t need_reset;
1027   svn_stream_t *base_stream;
1028   svn_stream_t *local_stream;
1029 } open_txdelta_stream_baton_t;
1030
1031 /* Implements svn_txdelta_stream_open_func_t */
1032 static svn_error_t *
1033 open_txdelta_stream(svn_txdelta_stream_t **txdelta_stream_p,
1034                     void *baton,
1035                     apr_pool_t *result_pool,
1036                     apr_pool_t *scratch_pool)
1037 {
1038   open_txdelta_stream_baton_t *b = baton;
1039
1040   if (b->need_reset)
1041     {
1042       /* Under rare circumstances, we can be restarted and would need to
1043        * supply the delta stream again.  In this case, reset both streams. */
1044       SVN_ERR(svn_stream_reset(b->base_stream));
1045       SVN_ERR(svn_stream_reset(b->local_stream));
1046     }
1047
1048   svn_txdelta2(txdelta_stream_p, b->base_stream, b->local_stream,
1049                FALSE, result_pool);
1050   b->need_reset = TRUE;
1051   return SVN_NO_ERROR;
1052 }
1053
1054 svn_error_t *
1055 svn_wc__internal_transmit_text_deltas(svn_stream_t *tempstream,
1056                                       const svn_checksum_t **new_text_base_md5_checksum,
1057                                       const svn_checksum_t **new_text_base_sha1_checksum,
1058                                       svn_wc__db_t *db,
1059                                       const char *local_abspath,
1060                                       svn_boolean_t fulltext,
1061                                       const svn_delta_editor_t *editor,
1062                                       void *file_baton,
1063                                       apr_pool_t *result_pool,
1064                                       apr_pool_t *scratch_pool)
1065 {
1066   const svn_checksum_t *expected_md5_checksum;  /* recorded MD5 of BASE_S. */
1067   svn_checksum_t *verify_checksum;  /* calc'd MD5 of BASE_STREAM */
1068   svn_checksum_t *local_md5_checksum;  /* calc'd MD5 of LOCAL_STREAM */
1069   svn_checksum_t *local_sha1_checksum;  /* calc'd SHA1 of LOCAL_STREAM */
1070   svn_wc__db_install_data_t *install_data = NULL;
1071   svn_error_t *err;
1072   svn_error_t *err2;
1073   svn_stream_t *base_stream;  /* delta source */
1074   svn_stream_t *local_stream;  /* delta target: LOCAL_ABSPATH transl. to NF */
1075
1076   /* Translated input */
1077   SVN_ERR(svn_wc__internal_translated_stream(&local_stream, db,
1078                                              local_abspath, local_abspath,
1079                                              SVN_WC_TRANSLATE_TO_NF,
1080                                              scratch_pool, scratch_pool));
1081
1082   /* If the caller wants a copy of the working file translated to
1083    * repository-normal form, make the copy by tee-ing the TEMPSTREAM.
1084    * This is only needed for the 1.6 API.  Even when using the 1.6 API
1085    * this temporary file is not used by the functions that would have used
1086    * it when using the 1.6 code.  It's possible that 3rd party users (if
1087    * there are any) might expect this file to be a text-base. */
1088   if (tempstream)
1089     {
1090       /* Wrap the translated stream with a new stream that writes the
1091          translated contents into the new text base file as we read from it.
1092          Note that the new text base file will be closed when the new stream
1093          is closed. */
1094       local_stream = copying_stream(local_stream, tempstream, scratch_pool);
1095     }
1096   if (new_text_base_sha1_checksum)
1097     {
1098       svn_stream_t *new_pristine_stream;
1099
1100       SVN_ERR(svn_wc__db_pristine_prepare_install(&new_pristine_stream,
1101                                                   &install_data,
1102                                                   &local_sha1_checksum, NULL,
1103                                                   db, local_abspath,
1104                                                   scratch_pool, scratch_pool));
1105       local_stream = copying_stream(local_stream, new_pristine_stream,
1106                                     scratch_pool);
1107     }
1108
1109   /* If sending a full text is requested, or if there is no pristine text
1110    * (e.g. the node is locally added), then set BASE_STREAM to an empty
1111    * stream and leave EXPECTED_MD5_CHECKSUM and VERIFY_CHECKSUM as NULL.
1112    *
1113    * Otherwise, set BASE_STREAM to a stream providing the base (source) text
1114    * for the delta, set EXPECTED_MD5_CHECKSUM to its stored MD5 checksum,
1115    * and arrange for its VERIFY_CHECKSUM to be calculated later. */
1116   if (! fulltext)
1117     {
1118       /* We will be computing a delta against the pristine contents */
1119       /* We need the expected checksum to be an MD-5 checksum rather than a
1120        * SHA-1 because we want to pass it to apply_textdelta(). */
1121       SVN_ERR(read_and_checksum_pristine_text(&base_stream,
1122                                               &expected_md5_checksum,
1123                                               &verify_checksum,
1124                                               db, local_abspath,
1125                                               scratch_pool, scratch_pool));
1126     }
1127   else
1128     {
1129       /* Send a fulltext. */
1130       base_stream = svn_stream_empty(scratch_pool);
1131       expected_md5_checksum = NULL;
1132       verify_checksum = NULL;
1133     }
1134
1135   /* Arrange the stream to calculate the resulting MD5. */
1136   local_stream = svn_stream_checksummed2(local_stream, &local_md5_checksum,
1137                                          NULL, svn_checksum_md5, TRUE,
1138                                          scratch_pool);
1139
1140   /* Tell the editor to apply a textdelta stream to the file baton. */
1141   {
1142     open_txdelta_stream_baton_t baton = { 0 };
1143
1144     /* apply_textdelta_stream() is working against a base with this checksum */
1145     const char *base_digest_hex = NULL;
1146
1147     if (expected_md5_checksum)
1148       /* ### Why '..._display()'?  expected_md5_checksum should never be all-
1149        * zero, but if it is, we would want to pass NULL not an all-zero
1150        * digest to apply_textdelta_stream(), wouldn't we? */
1151       base_digest_hex = svn_checksum_to_cstring_display(expected_md5_checksum,
1152                                                         scratch_pool);
1153
1154     baton.need_reset = FALSE;
1155     baton.base_stream = svn_stream_disown(base_stream, scratch_pool);
1156     baton.local_stream = svn_stream_disown(local_stream, scratch_pool);
1157     err = editor->apply_textdelta_stream(editor, file_baton, base_digest_hex,
1158                                          open_txdelta_stream, &baton,
1159                                          scratch_pool);
1160   }
1161
1162   /* Close the two streams to force writing the digest */
1163   err2 = svn_stream_close(base_stream);
1164   if (err2)
1165     {
1166       /* Set verify_checksum to NULL if svn_stream_close() returns error
1167          because checksum will be uninitialized in this case. */
1168       verify_checksum = NULL;
1169       err = svn_error_compose_create(err, err2);
1170     }
1171
1172   err = svn_error_compose_create(err, svn_stream_close(local_stream));
1173
1174   /* If we have an error, it may be caused by a corrupt text base,
1175      so check the checksum. */
1176   if (expected_md5_checksum && verify_checksum
1177       && !svn_checksum_match(expected_md5_checksum, verify_checksum))
1178     {
1179       /* The entry checksum does not match the actual text
1180          base checksum.  Extreme badness. Of course,
1181          theoretically we could just switch to
1182          fulltext transmission here, and everything would
1183          work fine; after all, we're going to replace the
1184          text base with a new one in a moment anyway, and
1185          we'd fix the checksum then.  But it's better to
1186          error out.  People should know that their text
1187          bases are getting corrupted, so they can
1188          investigate.  Other commands could be affected,
1189          too, such as `svn diff'.  */
1190
1191       err = svn_error_compose_create(
1192               svn_checksum_mismatch_err(expected_md5_checksum, verify_checksum,
1193                             scratch_pool,
1194                             _("Checksum mismatch for text base of '%s'"),
1195                             svn_dirent_local_style(local_abspath,
1196                                                    scratch_pool)),
1197               err);
1198
1199       return svn_error_create(SVN_ERR_WC_CORRUPT_TEXT_BASE, err, NULL);
1200     }
1201
1202   /* Now, handle that delta transmission error if any, so we can stop
1203      thinking about it after this point. */
1204   SVN_ERR_W(err, apr_psprintf(scratch_pool,
1205                               _("While preparing '%s' for commit"),
1206                               svn_dirent_local_style(local_abspath,
1207                                                      scratch_pool)));
1208
1209   if (new_text_base_md5_checksum)
1210     *new_text_base_md5_checksum = svn_checksum_dup(local_md5_checksum,
1211                                                    result_pool);
1212   if (new_text_base_sha1_checksum)
1213     {
1214       SVN_ERR(svn_wc__db_pristine_install(install_data,
1215                                           local_sha1_checksum,
1216                                           local_md5_checksum,
1217                                           scratch_pool));
1218       *new_text_base_sha1_checksum = svn_checksum_dup(local_sha1_checksum,
1219                                                       result_pool);
1220     }
1221
1222   /* Close the file baton, and get outta here. */
1223   return svn_error_trace(
1224              editor->close_file(file_baton,
1225                                 svn_checksum_to_cstring(local_md5_checksum,
1226                                                         scratch_pool),
1227                                 scratch_pool));
1228 }
1229
1230 svn_error_t *
1231 svn_wc_transmit_text_deltas3(const svn_checksum_t **new_text_base_md5_checksum,
1232                              const svn_checksum_t **new_text_base_sha1_checksum,
1233                              svn_wc_context_t *wc_ctx,
1234                              const char *local_abspath,
1235                              svn_boolean_t fulltext,
1236                              const svn_delta_editor_t *editor,
1237                              void *file_baton,
1238                              apr_pool_t *result_pool,
1239                              apr_pool_t *scratch_pool)
1240 {
1241   return svn_wc__internal_transmit_text_deltas(NULL,
1242                                                new_text_base_md5_checksum,
1243                                                new_text_base_sha1_checksum,
1244                                                wc_ctx->db, local_abspath,
1245                                                fulltext, editor,
1246                                                file_baton, result_pool,
1247                                                scratch_pool);
1248 }
1249
1250 svn_error_t *
1251 svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db,
1252                                      const char *local_abspath,
1253                                      const svn_delta_editor_t *editor,
1254                                      void *baton,
1255                                      apr_pool_t *scratch_pool)
1256 {
1257   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1258   int i;
1259   apr_array_header_t *propmods;
1260   svn_node_kind_t kind;
1261
1262   SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath,
1263                                FALSE /* allow_missing */,
1264                                FALSE /* show_deleted */,
1265                                FALSE /* show_hidden */,
1266                                iterpool));
1267
1268   if (kind == svn_node_none)
1269     return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1270                              _("The node '%s' was not found."),
1271                              svn_dirent_local_style(local_abspath, iterpool));
1272
1273   /* Get an array of local changes by comparing the hashes. */
1274   SVN_ERR(svn_wc__internal_propdiff(&propmods, NULL, db, local_abspath,
1275                                     scratch_pool, iterpool));
1276
1277   /* Apply each local change to the baton */
1278   for (i = 0; i < propmods->nelts; i++)
1279     {
1280       const svn_prop_t *p = &APR_ARRAY_IDX(propmods, i, svn_prop_t);
1281
1282       svn_pool_clear(iterpool);
1283
1284       if (kind == svn_node_file)
1285         SVN_ERR(editor->change_file_prop(baton, p->name, p->value,
1286                                          iterpool));
1287       else
1288         SVN_ERR(editor->change_dir_prop(baton, p->name, p->value,
1289                                         iterpool));
1290     }
1291
1292   svn_pool_destroy(iterpool);
1293   return SVN_NO_ERROR;
1294 }
1295
1296 svn_error_t *
1297 svn_wc_transmit_prop_deltas2(svn_wc_context_t *wc_ctx,
1298                              const char *local_abspath,
1299                              const svn_delta_editor_t *editor,
1300                              void *baton,
1301                              apr_pool_t *scratch_pool)
1302 {
1303   return svn_wc__internal_transmit_prop_deltas(wc_ctx->db, local_abspath,
1304                                                editor, baton, scratch_pool);
1305 }