]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_wc/adm_crawler.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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              apr_pool_t *scratch_pool)
73 {
74   svn_skel_t *work_item;
75
76   SVN_ERR(svn_wc__wq_build_file_install(&work_item,
77                                         db, local_abspath,
78                                         NULL /* source_abspath */,
79                                         use_commit_times,
80                                         TRUE /* record_fileinfo */,
81                                         scratch_pool, scratch_pool));
82   /* ### we need an existing path for wq_add. not entirely WRI_ABSPATH yet  */
83   SVN_ERR(svn_wc__db_wq_add(db,
84                             svn_dirent_dirname(local_abspath, scratch_pool),
85                             work_item, scratch_pool));
86
87   /* Run the work item immediately.  */
88   SVN_ERR(svn_wc__wq_run(db, local_abspath,
89                          NULL, NULL, /* ### nice to have cancel_func/baton */
90                          scratch_pool));
91
92   /* Remove any text conflict */
93   if (mark_resolved_text_conflict)
94     SVN_ERR(svn_wc__mark_resolved_text_conflict(db, local_abspath, scratch_pool));
95
96   return SVN_NO_ERROR;
97 }
98
99 svn_error_t *
100 svn_wc_restore(svn_wc_context_t *wc_ctx,
101                const char *local_abspath,
102                svn_boolean_t use_commit_times,
103                apr_pool_t *scratch_pool)
104 {
105   svn_wc__db_status_t status;
106   svn_node_kind_t kind;
107   svn_node_kind_t disk_kind;
108   const svn_checksum_t *checksum;
109
110   SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
111
112   if (disk_kind != svn_node_none)
113     return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
114                              _("The existing node '%s' can not be restored."),
115                              svn_dirent_local_style(local_abspath,
116                                                     scratch_pool));
117
118   SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
119                                NULL, NULL, NULL, &checksum, NULL, NULL, NULL, NULL,
120                                NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
121                                NULL, NULL, NULL, NULL,
122                                wc_ctx->db, local_abspath,
123                                scratch_pool, scratch_pool));
124
125   if (status != svn_wc__db_status_normal
126       && !((status == svn_wc__db_status_added
127             || status == svn_wc__db_status_incomplete)
128            && (kind == svn_node_dir
129                || (kind == svn_node_file && checksum != NULL)
130                /* || (kind == svn_node_symlink && target)*/)))
131     {
132       return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
133                                _("The node '%s' can not be restored."),
134                                svn_dirent_local_style(local_abspath,
135                                                       scratch_pool));
136     }
137
138   if (kind == svn_node_file || kind == svn_node_symlink)
139     SVN_ERR(restore_file(wc_ctx->db, local_abspath, use_commit_times,
140                          FALSE /*mark_resolved_text_conflict*/,
141                          scratch_pool));
142   else
143     SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
144
145   return SVN_NO_ERROR;
146 }
147
148 /* Try to restore LOCAL_ABSPATH of node type KIND and if successfull,
149    notify that the node is restored.  Use DB for accessing the working copy.
150    If USE_COMMIT_TIMES is set, then set working file's timestamp to
151    last-commit-time.
152
153    This function does all temporary allocations in SCRATCH_POOL
154  */
155 static svn_error_t *
156 restore_node(svn_wc__db_t *db,
157              const char *local_abspath,
158              svn_node_kind_t kind,
159              svn_boolean_t use_commit_times,
160              svn_wc_notify_func2_t notify_func,
161              void *notify_baton,
162              apr_pool_t *scratch_pool)
163 {
164   if (kind == svn_node_file || kind == svn_node_symlink)
165     {
166       /* Recreate file from text-base; mark any text conflict as resolved */
167       SVN_ERR(restore_file(db, local_abspath, use_commit_times,
168                            TRUE /*mark_resolved_text_conflict*/,
169                            scratch_pool));
170     }
171   else if (kind == svn_node_dir)
172     {
173       /* Recreating a directory is just a mkdir */
174       SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
175     }
176
177   /* ... report the restoration to the caller.  */
178   if (notify_func != NULL)
179     {
180       svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
181                                                      svn_wc_notify_restore,
182                                                      scratch_pool);
183       notify->kind = svn_node_file;
184       (*notify_func)(notify_baton, notify, scratch_pool);
185     }
186
187   return SVN_NO_ERROR;
188 }
189
190 /* The recursive crawler that describes a mixed-revision working
191    copy to an RA layer.  Used to initiate updates.
192
193    This is a depth-first recursive walk of the children of DIR_ABSPATH
194    (not including DIR_ABSPATH itself) using DB.  Look at each node and
195    check if its revision is different than DIR_REV.  If so, report this
196    fact to REPORTER.  If a node has a different URL than expected, or
197    a different depth than its parent, report that to REPORTER.
198
199    Report DIR_ABSPATH to the reporter as REPORT_RELPATH.
200
201    Alternatively, if REPORT_EVERYTHING is set, then report all
202    children unconditionally.
203
204    DEPTH is actually the *requested* depth for the update-like
205    operation for which we are reporting working copy state.  However,
206    certain requested depths affect the depth of the report crawl.  For
207    example, if the requested depth is svn_depth_empty, there's no
208    point descending into subdirs, no matter what their depths.  So:
209
210    If DEPTH is svn_depth_empty, don't report any files and don't
211    descend into any subdirs.  If svn_depth_files, report files but
212    still don't descend into subdirs.  If svn_depth_immediates, report
213    files, and report subdirs themselves but not their entries.  If
214    svn_depth_infinity or svn_depth_unknown, report everything all the
215    way down.  (That last sentence might sound counterintuitive, but
216    since you can't go deeper than the local ambient depth anyway,
217    requesting svn_depth_infinity really means "as deep as the various
218    parts of this working copy go".  Of course, the information that
219    comes back from the server will be different for svn_depth_unknown
220    than for svn_depth_infinity.)
221
222    DIR_REPOS_RELPATH, DIR_REPOS_ROOT and DIR_DEPTH are the repository
223    relative path, the repository root and depth stored on the directory,
224    passed here to avoid another database query.
225
226    DEPTH_COMPATIBILITY_TRICK means the same thing here as it does
227    in svn_wc_crawl_revisions5().
228
229    If RESTORE_FILES is set, then unexpectedly missing working files
230    will be restored from text-base and NOTIFY_FUNC/NOTIFY_BATON
231    will be called to report the restoration.  USE_COMMIT_TIMES is
232    passed to restore_file() helper. */
233 static svn_error_t *
234 report_revisions_and_depths(svn_wc__db_t *db,
235                             const char *dir_abspath,
236                             const char *report_relpath,
237                             svn_revnum_t dir_rev,
238                             const char *dir_repos_relpath,
239                             const char *dir_repos_root,
240                             svn_depth_t dir_depth,
241                             const svn_ra_reporter3_t *reporter,
242                             void *report_baton,
243                             svn_boolean_t restore_files,
244                             svn_depth_t depth,
245                             svn_boolean_t honor_depth_exclude,
246                             svn_boolean_t depth_compatibility_trick,
247                             svn_boolean_t report_everything,
248                             svn_boolean_t use_commit_times,
249                             svn_cancel_func_t cancel_func,
250                             void *cancel_baton,
251                             svn_wc_notify_func2_t notify_func,
252                             void *notify_baton,
253                             apr_pool_t *scratch_pool)
254 {
255   apr_hash_t *base_children;
256   apr_hash_t *dirents;
257   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
258   apr_hash_index_t *hi;
259   svn_error_t *err;
260
261
262   /* Get both the SVN Entries and the actual on-disk entries.   Also
263      notice that we're picking up hidden entries too (read_children never
264      hides children). */
265   SVN_ERR(svn_wc__db_base_get_children_info(&base_children, db, dir_abspath,
266                                             scratch_pool, iterpool));
267
268   if (restore_files)
269     {
270       err = svn_io_get_dirents3(&dirents, dir_abspath, TRUE,
271                                 scratch_pool, scratch_pool);
272
273       if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
274                   || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
275         {
276           svn_error_clear(err);
277           /* There is no directory, and if we could create the directory
278              we would have already created it when walking the parent
279              directory */
280           restore_files = FALSE;
281           dirents = NULL;
282         }
283       else
284         SVN_ERR(err);
285     }
286   else
287     dirents = NULL;
288
289   /*** Do the real reporting and recursing. ***/
290
291   /* Looping over current directory's BASE children: */
292   for (hi = apr_hash_first(scratch_pool, base_children);
293        hi != NULL;
294        hi = apr_hash_next(hi))
295     {
296       const char *child = svn__apr_hash_index_key(hi);
297       const char *this_report_relpath;
298       const char *this_abspath;
299       svn_boolean_t this_switched = FALSE;
300       struct svn_wc__db_base_info_t *ths = svn__apr_hash_index_val(hi);
301
302       if (cancel_func)
303         SVN_ERR(cancel_func(cancel_baton));
304
305       /* Clear the iteration subpool here because the loop has a bunch
306          of 'continue' jump statements. */
307       svn_pool_clear(iterpool);
308
309       /* Compute the paths and URLs we need. */
310       this_report_relpath = svn_relpath_join(report_relpath, child, iterpool);
311       this_abspath = svn_dirent_join(dir_abspath, child, iterpool);
312
313       /*** File Externals **/
314       if (ths->update_root)
315         {
316           /* File externals are ... special.  We ignore them. */;
317           continue;
318         }
319
320       /* First check for exclusion */
321       if (ths->status == svn_wc__db_status_excluded)
322         {
323           if (honor_depth_exclude)
324             {
325               /* Report the excluded path, no matter whether report_everything
326                  flag is set.  Because the report_everything flag indicates
327                  that the server will treat the wc as empty and thus push
328                  full content of the files/subdirs. But we want to prevent the
329                  server from pushing the full content of this_path at us. */
330
331               /* The server does not support link_path report on excluded
332                  path. We explicitly prohibit this situation in
333                  svn_wc_crop_tree(). */
334               SVN_ERR(reporter->set_path(report_baton,
335                                          this_report_relpath,
336                                          dir_rev,
337                                          svn_depth_exclude,
338                                          FALSE,
339                                          NULL,
340                                          iterpool));
341             }
342           else
343             {
344               /* We want to pull in the excluded target. So, report it as
345                  deleted, and server will respond properly. */
346               if (! report_everything)
347                 SVN_ERR(reporter->delete_path(report_baton,
348                                               this_report_relpath, iterpool));
349             }
350           continue;
351         }
352
353       /*** The Big Tests: ***/
354       if (ths->status == svn_wc__db_status_server_excluded
355           || ths->status == svn_wc__db_status_not_present)
356         {
357           /* If the entry is 'absent' or 'not-present', make sure the server
358              knows it's gone...
359              ...unless we're reporting everything, in which case we're
360              going to report it missing later anyway.
361
362              This instructs the server to send it back to us, if it is
363              now available (an addition after a not-present state), or if
364              it is now authorized (change in authz for the absent item).  */
365           if (! report_everything)
366             SVN_ERR(reporter->delete_path(report_baton, this_report_relpath,
367                                           iterpool));
368           continue;
369         }
370
371       /* Is the entry NOT on the disk? We may be able to restore it.  */
372       if (restore_files
373           && svn_hash_gets(dirents, child) == NULL)
374         {
375           svn_wc__db_status_t wrk_status;
376           svn_node_kind_t wrk_kind;
377           const svn_checksum_t *checksum;
378
379           SVN_ERR(svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL,
380                                        NULL, NULL, NULL, NULL, NULL, NULL,
381                                        &checksum, NULL, NULL, NULL, NULL, NULL,
382                                        NULL, NULL, NULL, NULL, NULL, NULL,
383                                        NULL, NULL, NULL, NULL, NULL,
384                                        db, this_abspath, iterpool, iterpool));
385
386           if ((wrk_status == svn_wc__db_status_normal
387                || wrk_status == svn_wc__db_status_added
388                || wrk_status == svn_wc__db_status_incomplete)
389               && (wrk_kind == svn_node_dir || checksum))
390             {
391               svn_node_kind_t dirent_kind;
392
393               /* It is possible on a case insensitive system that the
394                  entry is not really missing, but just cased incorrectly.
395                  In this case we can't overwrite it with the pristine
396                  version */
397               SVN_ERR(svn_io_check_path(this_abspath, &dirent_kind, iterpool));
398
399               if (dirent_kind == svn_node_none)
400                 {
401                   SVN_ERR(restore_node(db, this_abspath, wrk_kind,
402                                        use_commit_times, notify_func,
403                                        notify_baton, iterpool));
404                 }
405             }
406         }
407
408       /* And finally prepare for reporting */
409       if (!ths->repos_relpath)
410         {
411           ths->repos_relpath = svn_relpath_join(dir_repos_relpath, child,
412                                                 iterpool);
413         }
414       else
415         {
416           const char *childname
417             = svn_relpath_skip_ancestor(dir_repos_relpath, ths->repos_relpath);
418
419           if (childname == NULL || strcmp(childname, child) != 0)
420             {
421               this_switched = TRUE;
422             }
423         }
424
425       /* Tweak THIS_DEPTH to a useful value.  */
426       if (ths->depth == svn_depth_unknown)
427         ths->depth = svn_depth_infinity;
428
429       /*** Files ***/
430       if (ths->kind == svn_node_file
431           || ths->kind == svn_node_symlink)
432         {
433           if (report_everything)
434             {
435               /* Report the file unconditionally, one way or another. */
436               if (this_switched)
437                 SVN_ERR(reporter->link_path(report_baton,
438                                             this_report_relpath,
439                                             svn_path_url_add_component2(
440                                                 dir_repos_root,
441                                                 ths->repos_relpath, iterpool),
442                                             ths->revnum,
443                                             ths->depth,
444                                             FALSE,
445                                             ths->lock ? ths->lock->token : NULL,
446                                             iterpool));
447               else
448                 SVN_ERR(reporter->set_path(report_baton,
449                                            this_report_relpath,
450                                            ths->revnum,
451                                            ths->depth,
452                                            FALSE,
453                                            ths->lock ? ths->lock->token : NULL,
454                                            iterpool));
455             }
456
457           /* Possibly report a disjoint URL ... */
458           else if (this_switched)
459             SVN_ERR(reporter->link_path(report_baton,
460                                         this_report_relpath,
461                                         svn_path_url_add_component2(
462                                                 dir_repos_root,
463                                                 ths->repos_relpath, iterpool),
464                                         ths->revnum,
465                                         ths->depth,
466                                         FALSE,
467                                         ths->lock ? ths->lock->token : NULL,
468                                         iterpool));
469           /* ... or perhaps just a differing revision or lock token,
470              or the mere presence of the file in a depth-empty dir. */
471           else if (ths->revnum != dir_rev
472                    || ths->lock
473                    || dir_depth == svn_depth_empty)
474             SVN_ERR(reporter->set_path(report_baton,
475                                        this_report_relpath,
476                                        ths->revnum,
477                                        ths->depth,
478                                        FALSE,
479                                        ths->lock ? ths->lock->token : NULL,
480                                        iterpool));
481         } /* end file case */
482
483       /*** Directories (in recursive mode) ***/
484       else if (ths->kind == svn_node_dir
485                && (depth > svn_depth_files
486                    || depth == svn_depth_unknown))
487         {
488           svn_boolean_t is_incomplete;
489           svn_boolean_t start_empty;
490           svn_depth_t report_depth = ths->depth;
491
492           is_incomplete = (ths->status == svn_wc__db_status_incomplete);
493           start_empty = is_incomplete;
494
495           if (!SVN_DEPTH_IS_RECURSIVE(depth))
496             report_depth = svn_depth_empty;
497
498           /* When a <= 1.6 working copy is upgraded without some of its
499              subdirectories we miss some information in the database. If we
500              report the revision as -1, the update editor will receive an
501              add_directory() while it still knows the directory.
502
503              This would raise strange tree conflicts and probably assertions
504              as it would a BASE vs BASE conflict */
505           if (is_incomplete && !SVN_IS_VALID_REVNUM(ths->revnum))
506             ths->revnum = dir_rev;
507
508           if (depth_compatibility_trick
509               && ths->depth <= svn_depth_files
510               && depth > ths->depth)
511             {
512               start_empty = TRUE;
513             }
514
515           if (report_everything)
516             {
517               /* Report the dir unconditionally, one way or another... */
518               if (this_switched)
519                 SVN_ERR(reporter->link_path(report_baton,
520                                             this_report_relpath,
521                                             svn_path_url_add_component2(
522                                                 dir_repos_root,
523                                                 ths->repos_relpath, iterpool),
524                                             ths->revnum,
525                                             report_depth,
526                                             start_empty,
527                                             ths->lock ? ths->lock->token
528                                                       : NULL,
529                                             iterpool));
530               else
531                 SVN_ERR(reporter->set_path(report_baton,
532                                            this_report_relpath,
533                                            ths->revnum,
534                                            report_depth,
535                                            start_empty,
536                                            ths->lock ? ths->lock->token : NULL,
537                                            iterpool));
538             }
539           else if (this_switched)
540             {
541               /* ...or possibly report a disjoint URL ... */
542               SVN_ERR(reporter->link_path(report_baton,
543                                           this_report_relpath,
544                                           svn_path_url_add_component2(
545                                               dir_repos_root,
546                                               ths->repos_relpath, iterpool),
547                                           ths->revnum,
548                                           report_depth,
549                                           start_empty,
550                                           ths->lock ? ths->lock->token : NULL,
551                                           iterpool));
552             }
553           else if (ths->revnum != dir_rev
554                    || ths->lock
555                    || is_incomplete
556                    || dir_depth == svn_depth_empty
557                    || dir_depth == svn_depth_files
558                    || (dir_depth == svn_depth_immediates
559                        && ths->depth != svn_depth_empty)
560                    || (ths->depth < svn_depth_infinity
561                        && SVN_DEPTH_IS_RECURSIVE(depth)))
562             {
563               /* ... or perhaps just a differing revision, lock token,
564                  incomplete subdir, the mere presence of the directory
565                  in a depth-empty or depth-files dir, or if the parent
566                  dir is at depth-immediates but the child is not at
567                  depth-empty.  Also describe shallow subdirs if we are
568                  trying to set depth to infinity. */
569               SVN_ERR(reporter->set_path(report_baton,
570                                          this_report_relpath,
571                                          ths->revnum,
572                                          report_depth,
573                                          start_empty,
574                                          ths->lock ? ths->lock->token : NULL,
575                                          iterpool));
576             }
577
578           /* Finally, recurse if necessary and appropriate. */
579           if (SVN_DEPTH_IS_RECURSIVE(depth))
580             {
581               const char *repos_relpath = ths->repos_relpath;
582
583               if (repos_relpath == NULL)
584                 {
585                   repos_relpath = svn_relpath_join(dir_repos_relpath, child,
586                                                    iterpool);
587                 }
588
589               SVN_ERR(report_revisions_and_depths(db,
590                                                   this_abspath,
591                                                   this_report_relpath,
592                                                   ths->revnum,
593                                                   repos_relpath,
594                                                   dir_repos_root,
595                                                   ths->depth,
596                                                   reporter, report_baton,
597                                                   restore_files, depth,
598                                                   honor_depth_exclude,
599                                                   depth_compatibility_trick,
600                                                   start_empty,
601                                                   use_commit_times,
602                                                   cancel_func, cancel_baton,
603                                                   notify_func, notify_baton,
604                                                   iterpool));
605             }
606         } /* end directory case */
607     } /* end main entries loop */
608
609   /* We're done examining this dir's entries, so free everything. */
610   svn_pool_destroy(iterpool);
611
612   return SVN_NO_ERROR;
613 }
614
615 \f
616 /*------------------------------------------------------------------*/
617 /*** Public Interfaces ***/
618
619
620 svn_error_t *
621 svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx,
622                         const char *local_abspath,
623                         const svn_ra_reporter3_t *reporter,
624                         void *report_baton,
625                         svn_boolean_t restore_files,
626                         svn_depth_t depth,
627                         svn_boolean_t honor_depth_exclude,
628                         svn_boolean_t depth_compatibility_trick,
629                         svn_boolean_t use_commit_times,
630                         svn_cancel_func_t cancel_func,
631                         void *cancel_baton,
632                         svn_wc_notify_func2_t notify_func,
633                         void *notify_baton,
634                         apr_pool_t *scratch_pool)
635 {
636   svn_wc__db_t *db = wc_ctx->db;
637   svn_error_t *fserr, *err;
638   svn_revnum_t target_rev = SVN_INVALID_REVNUM;
639   svn_boolean_t start_empty;
640   svn_wc__db_status_t status;
641   svn_node_kind_t target_kind;
642   const char *repos_relpath, *repos_root_url;
643   svn_depth_t target_depth;
644   svn_wc__db_lock_t *target_lock;
645   svn_node_kind_t disk_kind;
646   svn_depth_t report_depth;
647   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
648
649   /* Get the base rev, which is the first revnum that entries will be
650      compared to, and some other WC info about the target. */
651   err = svn_wc__db_base_get_info(&status, &target_kind, &target_rev,
652                                  &repos_relpath, &repos_root_url,
653                                  NULL, NULL, NULL, NULL, &target_depth,
654                                  NULL, NULL, &target_lock,
655                                  NULL, NULL, NULL,
656                                  db, local_abspath, scratch_pool,
657                                  scratch_pool);
658
659   if (err
660       || (status != svn_wc__db_status_normal
661           && status != svn_wc__db_status_incomplete))
662     {
663       if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
664         return svn_error_trace(err);
665
666       svn_error_clear(err);
667
668       /* We don't know about this node, so all we have to do is tell
669          the reporter that we don't know this node.
670
671          But first we have to start the report by sending some basic
672          information for the root. */
673
674       if (depth == svn_depth_unknown)
675         depth = svn_depth_infinity;
676
677       SVN_ERR(reporter->set_path(report_baton, "", 0, depth, FALSE,
678                                  NULL, scratch_pool));
679       SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
680
681       /* Finish the report, which causes the update editor to be
682          driven. */
683       SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
684
685       return SVN_NO_ERROR;
686     }
687
688   if (target_depth == svn_depth_unknown)
689     target_depth = svn_depth_infinity;
690
691   start_empty = (status == svn_wc__db_status_incomplete);
692   if (depth_compatibility_trick
693       && target_depth <= svn_depth_immediates
694       && depth > target_depth)
695     {
696       start_empty = TRUE;
697     }
698
699   if (restore_files)
700     SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
701   else
702     disk_kind = svn_node_unknown;
703
704   /* Determine if there is a missing node that should be restored */
705   if (restore_files
706       && disk_kind == svn_node_none)
707     {
708       svn_wc__db_status_t wrk_status;
709       svn_node_kind_t wrk_kind;
710       const svn_checksum_t *checksum;
711
712       err = svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL, NULL,
713                                  NULL, NULL, NULL, NULL, NULL, &checksum, NULL,
714                                  NULL, NULL, NULL, NULL, NULL, NULL, NULL,
715                                  NULL, NULL, NULL, NULL, NULL, NULL, NULL,
716                                  NULL,
717                                  db, local_abspath,
718                                  scratch_pool, scratch_pool);
719
720
721       if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
722         {
723           svn_error_clear(err);
724           wrk_status = svn_wc__db_status_not_present;
725           wrk_kind = svn_node_file;
726         }
727       else
728         SVN_ERR(err);
729
730       if ((wrk_status == svn_wc__db_status_normal
731           || wrk_status == svn_wc__db_status_added
732           || wrk_status == svn_wc__db_status_incomplete)
733           && (wrk_kind == svn_node_dir || checksum))
734         {
735           SVN_ERR(restore_node(wc_ctx->db, local_abspath,
736                                wrk_kind, use_commit_times,
737                                notify_func, notify_baton,
738                                scratch_pool));
739         }
740     }
741
742   {
743     report_depth = target_depth;
744
745     if (honor_depth_exclude
746         && depth != svn_depth_unknown
747         && depth < target_depth)
748       report_depth = depth;
749
750     /* The first call to the reporter merely informs it that the
751        top-level directory being updated is at BASE_REV.  Its PATH
752        argument is ignored. */
753     SVN_ERR(reporter->set_path(report_baton, "", target_rev, report_depth,
754                                start_empty, NULL, scratch_pool));
755   }
756   if (target_kind == svn_node_dir)
757     {
758       if (depth != svn_depth_empty)
759         {
760           /* Recursively crawl ROOT_DIRECTORY and report differing
761              revisions. */
762           err = report_revisions_and_depths(wc_ctx->db,
763                                             local_abspath,
764                                             "",
765                                             target_rev,
766                                             repos_relpath,
767                                             repos_root_url,
768                                             report_depth,
769                                             reporter, report_baton,
770                                             restore_files, depth,
771                                             honor_depth_exclude,
772                                             depth_compatibility_trick,
773                                             start_empty,
774                                             use_commit_times,
775                                             cancel_func, cancel_baton,
776                                             notify_func, notify_baton,
777                                             scratch_pool);
778           if (err)
779             goto abort_report;
780         }
781     }
782
783   else if (target_kind == svn_node_file || target_kind == svn_node_symlink)
784     {
785       const char *parent_abspath, *base;
786       svn_wc__db_status_t parent_status;
787       const char *parent_repos_relpath;
788
789       svn_dirent_split(&parent_abspath, &base, local_abspath,
790                        scratch_pool);
791
792       /* We can assume a file is in the same repository as its parent
793          directory, so we only look at the relpath. */
794       err = svn_wc__db_base_get_info(&parent_status, NULL, NULL,
795                                      &parent_repos_relpath, NULL, NULL, NULL,
796                                      NULL, NULL, NULL, NULL, NULL, NULL,
797                                      NULL, NULL, NULL,
798                                      db, parent_abspath,
799                                      scratch_pool, scratch_pool);
800
801       if (err)
802         goto abort_report;
803
804       if (strcmp(repos_relpath,
805                  svn_relpath_join(parent_repos_relpath, base,
806                                   scratch_pool)) != 0)
807         {
808           /* This file is disjoint with respect to its parent
809              directory.  Since we are looking at the actual target of
810              the report (not some file in a subdirectory of a target
811              directory), and that target is a file, we need to pass an
812              empty string to link_path. */
813           err = reporter->link_path(report_baton,
814                                     "",
815                                     svn_path_url_add_component2(
816                                                     repos_root_url,
817                                                     repos_relpath,
818                                                     scratch_pool),
819                                     target_rev,
820                                     svn_depth_infinity,
821                                     FALSE,
822                                     target_lock ? target_lock->token : NULL,
823                                     scratch_pool);
824           if (err)
825             goto abort_report;
826         }
827       else if (target_lock)
828         {
829           /* If this entry is a file node, we just want to report that
830              node's revision.  Since we are looking at the actual target
831              of the report (not some file in a subdirectory of a target
832              directory), and that target is a file, we need to pass an
833              empty string to set_path. */
834           err = reporter->set_path(report_baton, "", target_rev,
835                                    svn_depth_infinity,
836                                    FALSE,
837                                    target_lock ? target_lock->token : NULL,
838                                    scratch_pool);
839           if (err)
840             goto abort_report;
841         }
842     }
843
844   /* Finish the report, which causes the update editor to be driven. */
845   return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
846
847  abort_report:
848   /* Clean up the fs transaction. */
849   if ((fserr = reporter->abort_report(report_baton, scratch_pool)))
850     {
851       fserr = svn_error_quick_wrap(fserr, _("Error aborting report"));
852       svn_error_compose(err, fserr);
853     }
854   return svn_error_trace(err);
855 }
856
857 /*** Copying stream ***/
858
859 /* A copying stream is a bit like the unix tee utility:
860  *
861  * It reads the SOURCE when asked for data and while returning it,
862  * also writes the same data to TARGET.
863  */
864 struct copying_stream_baton
865 {
866   /* Stream to read input from. */
867   svn_stream_t *source;
868
869   /* Stream to write all data read to. */
870   svn_stream_t *target;
871 };
872
873
874 /* */
875 static svn_error_t *
876 read_handler_copy(void *baton, char *buffer, apr_size_t *len)
877 {
878   struct copying_stream_baton *btn = baton;
879
880   SVN_ERR(svn_stream_read(btn->source, buffer, len));
881
882   return svn_stream_write(btn->target, buffer, len);
883 }
884
885 /* */
886 static svn_error_t *
887 close_handler_copy(void *baton)
888 {
889   struct copying_stream_baton *btn = baton;
890
891   SVN_ERR(svn_stream_close(btn->target));
892   return svn_stream_close(btn->source);
893 }
894
895
896 /* Return a stream - allocated in POOL - which reads its input
897  * from SOURCE and, while returning that to the caller, at the
898  * same time writes that to TARGET.
899  */
900 static svn_stream_t *
901 copying_stream(svn_stream_t *source,
902                svn_stream_t *target,
903                apr_pool_t *pool)
904 {
905   struct copying_stream_baton *baton;
906   svn_stream_t *stream;
907
908   baton = apr_palloc(pool, sizeof (*baton));
909   baton->source = source;
910   baton->target = target;
911
912   stream = svn_stream_create(baton, pool);
913   svn_stream_set_read(stream, read_handler_copy);
914   svn_stream_set_close(stream, close_handler_copy);
915
916   return stream;
917 }
918
919
920 /* Set *STREAM to a stream from which the caller can read the pristine text
921  * of the working version of the file at LOCAL_ABSPATH.  If the working
922  * version of LOCAL_ABSPATH has no pristine text because it is locally
923  * added, set *STREAM to an empty stream.  If the working version of
924  * LOCAL_ABSPATH is not a file, return an error.
925  *
926  * Set *EXPECTED_MD5_CHECKSUM to the recorded MD5 checksum.
927  *
928  * Arrange for the actual checksum of the text to be calculated and written
929  * into *ACTUAL_MD5_CHECKSUM when the stream is read.
930  */
931 static svn_error_t *
932 read_and_checksum_pristine_text(svn_stream_t **stream,
933                                 const svn_checksum_t **expected_md5_checksum,
934                                 svn_checksum_t **actual_md5_checksum,
935                                 svn_wc__db_t *db,
936                                 const char *local_abspath,
937                                 apr_pool_t *result_pool,
938                                 apr_pool_t *scratch_pool)
939 {
940   svn_stream_t *base_stream;
941
942   SVN_ERR(svn_wc__get_pristine_contents(&base_stream, NULL, db, local_abspath,
943                                         result_pool, scratch_pool));
944   if (base_stream == NULL)
945     {
946       base_stream = svn_stream_empty(result_pool);
947       *expected_md5_checksum = NULL;
948       *actual_md5_checksum = NULL;
949     }
950   else
951     {
952       const svn_checksum_t *expected_md5;
953
954       SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
955                                    NULL, NULL, NULL, NULL, &expected_md5,
956                                    NULL, NULL, NULL, NULL, NULL, NULL,
957                                    NULL, NULL, NULL, NULL, NULL, NULL,
958                                    NULL, NULL, NULL, NULL,
959                                    db, local_abspath,
960                                    result_pool, scratch_pool));
961       if (expected_md5 == NULL)
962         return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
963                                  _("Pristine checksum for file '%s' is missing"),
964                                  svn_dirent_local_style(local_abspath,
965                                                         scratch_pool));
966       if (expected_md5->kind != svn_checksum_md5)
967         SVN_ERR(svn_wc__db_pristine_get_md5(&expected_md5, db, local_abspath,
968                                             expected_md5,
969                                             result_pool, scratch_pool));
970       *expected_md5_checksum = expected_md5;
971
972       /* Arrange to set ACTUAL_MD5_CHECKSUM to the MD5 of what is *actually*
973          found when the base stream is read. */
974       base_stream = svn_stream_checksummed2(base_stream, actual_md5_checksum,
975                                             NULL, svn_checksum_md5, TRUE,
976                                             result_pool);
977     }
978
979   *stream = base_stream;
980   return SVN_NO_ERROR;
981 }
982
983
984 svn_error_t *
985 svn_wc__internal_transmit_text_deltas(const char **tempfile,
986                                       const svn_checksum_t **new_text_base_md5_checksum,
987                                       const svn_checksum_t **new_text_base_sha1_checksum,
988                                       svn_wc__db_t *db,
989                                       const char *local_abspath,
990                                       svn_boolean_t fulltext,
991                                       const svn_delta_editor_t *editor,
992                                       void *file_baton,
993                                       apr_pool_t *result_pool,
994                                       apr_pool_t *scratch_pool)
995 {
996   svn_txdelta_window_handler_t handler;
997   void *wh_baton;
998   const svn_checksum_t *expected_md5_checksum;  /* recorded MD5 of BASE_S. */
999   svn_checksum_t *verify_checksum;  /* calc'd MD5 of BASE_STREAM */
1000   svn_checksum_t *local_md5_checksum;  /* calc'd MD5 of LOCAL_STREAM */
1001   svn_checksum_t *local_sha1_checksum;  /* calc'd SHA1 of LOCAL_STREAM */
1002   const char *new_pristine_tmp_abspath;
1003   svn_error_t *err;
1004   svn_stream_t *base_stream;  /* delta source */
1005   svn_stream_t *local_stream;  /* delta target: LOCAL_ABSPATH transl. to NF */
1006
1007   /* Translated input */
1008   SVN_ERR(svn_wc__internal_translated_stream(&local_stream, db,
1009                                              local_abspath, local_abspath,
1010                                              SVN_WC_TRANSLATE_TO_NF,
1011                                              scratch_pool, scratch_pool));
1012
1013   /* If the caller wants a copy of the working file translated to
1014    * repository-normal form, make the copy by tee-ing the stream and set
1015    * *TEMPFILE to the path to it.  This is only needed for the 1.6 API,
1016    * 1.7 doesn't set TEMPFILE.  Even when using the 1.6 API this file
1017    * is not used by the functions that would have used it when using
1018    * the 1.6 code.  It's possible that 3rd party users (if there are any)
1019    * might expect this file to be a text-base. */
1020   if (tempfile)
1021     {
1022       svn_stream_t *tempstream;
1023
1024       /* It can't be the same location as in 1.6 because the admin directory
1025          no longer exists. */
1026       SVN_ERR(svn_stream_open_unique(&tempstream, tempfile,
1027                                      NULL, svn_io_file_del_none,
1028                                      result_pool, scratch_pool));
1029
1030       /* Wrap the translated stream with a new stream that writes the
1031          translated contents into the new text base file as we read from it.
1032          Note that the new text base file will be closed when the new stream
1033          is closed. */
1034       local_stream = copying_stream(local_stream, tempstream, scratch_pool);
1035     }
1036   if (new_text_base_sha1_checksum)
1037     {
1038       svn_stream_t *new_pristine_stream;
1039
1040       SVN_ERR(svn_wc__open_writable_base(&new_pristine_stream,
1041                                          &new_pristine_tmp_abspath,
1042                                          NULL, &local_sha1_checksum,
1043                                          db, local_abspath,
1044                                          scratch_pool, scratch_pool));
1045       local_stream = copying_stream(local_stream, new_pristine_stream,
1046                                     scratch_pool);
1047     }
1048
1049   /* If sending a full text is requested, or if there is no pristine text
1050    * (e.g. the node is locally added), then set BASE_STREAM to an empty
1051    * stream and leave EXPECTED_MD5_CHECKSUM and VERIFY_CHECKSUM as NULL.
1052    *
1053    * Otherwise, set BASE_STREAM to a stream providing the base (source) text
1054    * for the delta, set EXPECTED_MD5_CHECKSUM to its stored MD5 checksum,
1055    * and arrange for its VERIFY_CHECKSUM to be calculated later. */
1056   if (! fulltext)
1057     {
1058       /* We will be computing a delta against the pristine contents */
1059       /* We need the expected checksum to be an MD-5 checksum rather than a
1060        * SHA-1 because we want to pass it to apply_textdelta(). */
1061       SVN_ERR(read_and_checksum_pristine_text(&base_stream,
1062                                               &expected_md5_checksum,
1063                                               &verify_checksum,
1064                                               db, local_abspath,
1065                                               scratch_pool, scratch_pool));
1066     }
1067   else
1068     {
1069       /* Send a fulltext. */
1070       base_stream = svn_stream_empty(scratch_pool);
1071       expected_md5_checksum = NULL;
1072       verify_checksum = NULL;
1073     }
1074
1075   /* Tell the editor that we're about to apply a textdelta to the
1076      file baton; the editor returns to us a window consumer and baton.  */
1077   {
1078     /* apply_textdelta() is working against a base with this checksum */
1079     const char *base_digest_hex = NULL;
1080
1081     if (expected_md5_checksum)
1082       /* ### Why '..._display()'?  expected_md5_checksum should never be all-
1083        * zero, but if it is, we would want to pass NULL not an all-zero
1084        * digest to apply_textdelta(), wouldn't we? */
1085       base_digest_hex = svn_checksum_to_cstring_display(expected_md5_checksum,
1086                                                         scratch_pool);
1087
1088     SVN_ERR(editor->apply_textdelta(file_baton, base_digest_hex, scratch_pool,
1089                                     &handler, &wh_baton));
1090   }
1091
1092   /* Run diff processing, throwing windows at the handler. */
1093   err = svn_txdelta_run(base_stream, local_stream,
1094                         handler, wh_baton,
1095                         svn_checksum_md5, &local_md5_checksum,
1096                         NULL, NULL,
1097                         scratch_pool, scratch_pool);
1098
1099   /* Close the two streams to force writing the digest */
1100   err = svn_error_compose_create(err, svn_stream_close(base_stream));
1101   err = svn_error_compose_create(err, svn_stream_close(local_stream));
1102
1103   /* If we have an error, it may be caused by a corrupt text base,
1104      so check the checksum. */
1105   if (expected_md5_checksum && verify_checksum
1106       && !svn_checksum_match(expected_md5_checksum, verify_checksum))
1107     {
1108       /* The entry checksum does not match the actual text
1109          base checksum.  Extreme badness. Of course,
1110          theoretically we could just switch to
1111          fulltext transmission here, and everything would
1112          work fine; after all, we're going to replace the
1113          text base with a new one in a moment anyway, and
1114          we'd fix the checksum then.  But it's better to
1115          error out.  People should know that their text
1116          bases are getting corrupted, so they can
1117          investigate.  Other commands could be affected,
1118          too, such as `svn diff'.  */
1119
1120       if (tempfile)
1121         err = svn_error_compose_create(
1122                       err,
1123                       svn_io_remove_file2(*tempfile, TRUE, scratch_pool));
1124
1125       err = svn_error_compose_create(
1126               svn_checksum_mismatch_err(expected_md5_checksum, verify_checksum,
1127                             scratch_pool,
1128                             _("Checksum mismatch for text base of '%s'"),
1129                             svn_dirent_local_style(local_abspath,
1130                                                    scratch_pool)),
1131               err);
1132
1133       return svn_error_create(SVN_ERR_WC_CORRUPT_TEXT_BASE, err, NULL);
1134     }
1135
1136   /* Now, handle that delta transmission error if any, so we can stop
1137      thinking about it after this point. */
1138   SVN_ERR_W(err, apr_psprintf(scratch_pool,
1139                               _("While preparing '%s' for commit"),
1140                               svn_dirent_local_style(local_abspath,
1141                                                      scratch_pool)));
1142
1143   if (new_text_base_md5_checksum)
1144     *new_text_base_md5_checksum = svn_checksum_dup(local_md5_checksum,
1145                                                    result_pool);
1146   if (new_text_base_sha1_checksum)
1147     {
1148       SVN_ERR(svn_wc__db_pristine_install(db, new_pristine_tmp_abspath,
1149                                           local_sha1_checksum,
1150                                           local_md5_checksum,
1151                                           scratch_pool));
1152       *new_text_base_sha1_checksum = svn_checksum_dup(local_sha1_checksum,
1153                                                       result_pool);
1154     }
1155
1156   /* Close the file baton, and get outta here. */
1157   return svn_error_trace(
1158              editor->close_file(file_baton,
1159                                 svn_checksum_to_cstring(local_md5_checksum,
1160                                                         scratch_pool),
1161                                 scratch_pool));
1162 }
1163
1164 svn_error_t *
1165 svn_wc_transmit_text_deltas3(const svn_checksum_t **new_text_base_md5_checksum,
1166                              const svn_checksum_t **new_text_base_sha1_checksum,
1167                              svn_wc_context_t *wc_ctx,
1168                              const char *local_abspath,
1169                              svn_boolean_t fulltext,
1170                              const svn_delta_editor_t *editor,
1171                              void *file_baton,
1172                              apr_pool_t *result_pool,
1173                              apr_pool_t *scratch_pool)
1174 {
1175   return svn_wc__internal_transmit_text_deltas(NULL,
1176                                                new_text_base_md5_checksum,
1177                                                new_text_base_sha1_checksum,
1178                                                wc_ctx->db, local_abspath,
1179                                                fulltext, editor,
1180                                                file_baton, result_pool,
1181                                                scratch_pool);
1182 }
1183
1184 svn_error_t *
1185 svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db,
1186                                      const char *local_abspath,
1187                                      const svn_delta_editor_t *editor,
1188                                      void *baton,
1189                                      apr_pool_t *scratch_pool)
1190 {
1191   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1192   int i;
1193   apr_array_header_t *propmods;
1194   svn_node_kind_t kind;
1195
1196   SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath,
1197                                FALSE /* allow_missing */,
1198                                FALSE /* show_deleted */,
1199                                FALSE /* show_hidden */,
1200                                iterpool));
1201
1202   if (kind == svn_node_none)
1203     return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1204                              _("The node '%s' was not found."),
1205                              svn_dirent_local_style(local_abspath, iterpool));
1206
1207   /* Get an array of local changes by comparing the hashes. */
1208   SVN_ERR(svn_wc__internal_propdiff(&propmods, NULL, db, local_abspath,
1209                                     scratch_pool, iterpool));
1210
1211   /* Apply each local change to the baton */
1212   for (i = 0; i < propmods->nelts; i++)
1213     {
1214       const svn_prop_t *p = &APR_ARRAY_IDX(propmods, i, svn_prop_t);
1215
1216       svn_pool_clear(iterpool);
1217
1218       if (kind == svn_node_file)
1219         SVN_ERR(editor->change_file_prop(baton, p->name, p->value,
1220                                          iterpool));
1221       else
1222         SVN_ERR(editor->change_dir_prop(baton, p->name, p->value,
1223                                         iterpool));
1224     }
1225
1226   svn_pool_destroy(iterpool);
1227   return SVN_NO_ERROR;
1228 }
1229
1230 svn_error_t *
1231 svn_wc_transmit_prop_deltas2(svn_wc_context_t *wc_ctx,
1232                              const char *local_abspath,
1233                              const svn_delta_editor_t *editor,
1234                              void *baton,
1235                              apr_pool_t *scratch_pool)
1236 {
1237   return svn_wc__internal_transmit_prop_deltas(wc_ctx->db, local_abspath,
1238                                                editor, baton, scratch_pool);
1239 }