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