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.*/
67static svn_error_t *
68restore_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
103svn_error_t *
104svn_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 */
161static svn_error_t *
162restore_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. */
243static svn_error_t *
244report_revisions_and_depths(svn_wc__db_t *db,
245                            const char *dir_abspath,
246                            const char *report_relpath,
247                            svn_revnum_t dir_rev,
248                            const char *dir_repos_relpath,
249                            const char *dir_repos_root,
250                            svn_depth_t dir_depth,
251                            const svn_ra_reporter3_t *reporter,
252                            void *report_baton,
253                            svn_boolean_t restore_files,
254                            svn_depth_t depth,
255                            svn_boolean_t honor_depth_exclude,
256                            svn_boolean_t depth_compatibility_trick,
257                            svn_boolean_t report_everything,
258                            svn_boolean_t use_commit_times,
259                            svn_cancel_func_t cancel_func,
260                            void *cancel_baton,
261                            svn_wc_notify_func2_t notify_func,
262                            void *notify_baton,
263                            apr_pool_t *scratch_pool)
264{
265  apr_hash_t *base_children;
266  apr_hash_t *dirents;
267  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
268  apr_hash_index_t *hi;
269  svn_error_t *err;
270
271
272  /* Get both the SVN Entries and the actual on-disk entries.   Also
273     notice that we're picking up hidden entries too (read_children never
274     hides children). */
275  SVN_ERR(svn_wc__db_base_get_children_info(&base_children, db, dir_abspath,
276                                            scratch_pool, iterpool));
277
278  if (restore_files)
279    {
280      err = svn_io_get_dirents3(&dirents, dir_abspath, TRUE,
281                                scratch_pool, scratch_pool);
282
283      if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
284                  || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
285        {
286          svn_error_clear(err);
287          /* There is no directory, and if we could create the directory
288             we would have already created it when walking the parent
289             directory */
290          restore_files = FALSE;
291          dirents = NULL;
292        }
293      else
294        SVN_ERR(err);
295    }
296  else
297    dirents = NULL;
298
299  /*** Do the real reporting and recursing. ***/
300
301  /* Looping over current directory's BASE children: */
302  for (hi = apr_hash_first(scratch_pool, base_children);
303       hi != NULL;
304       hi = apr_hash_next(hi))
305    {
306      const char *child = apr_hash_this_key(hi);
307      const char *this_report_relpath;
308      const char *this_abspath;
309      svn_boolean_t this_switched = FALSE;
310      struct svn_wc__db_base_info_t *ths = apr_hash_this_val(hi);
311
312      if (cancel_func)
313        SVN_ERR(cancel_func(cancel_baton));
314
315      /* Clear the iteration subpool here because the loop has a bunch
316         of 'continue' jump statements. */
317      svn_pool_clear(iterpool);
318
319      /* Compute the paths and URLs we need. */
320      this_report_relpath = svn_relpath_join(report_relpath, child, iterpool);
321      this_abspath = svn_dirent_join(dir_abspath, child, iterpool);
322
323      /*** File Externals **/
324      if (ths->update_root)
325        {
326          /* File externals are ... special.  We ignore them. */;
327          continue;
328        }
329
330      /* First check for exclusion */
331      if (ths->status == svn_wc__db_status_excluded)
332        {
333          if (honor_depth_exclude)
334            {
335              /* Report the excluded path, no matter whether report_everything
336                 flag is set.  Because the report_everything flag indicates
337                 that the server will treat the wc as empty and thus push
338                 full content of the files/subdirs. But we want to prevent the
339                 server from pushing the full content of this_path at us. */
340
341              /* The server does not support link_path report on excluded
342                 path. We explicitly prohibit this situation in
343                 svn_wc_crop_tree(). */
344              SVN_ERR(reporter->set_path(report_baton,
345                                         this_report_relpath,
346                                         dir_rev,
347                                         svn_depth_exclude,
348                                         FALSE,
349                                         NULL,
350                                         iterpool));
351            }
352          else
353            {
354              /* We want to pull in the excluded target. So, report it as
355                 deleted, and server will respond properly. */
356              if (! report_everything)
357                SVN_ERR(reporter->delete_path(report_baton,
358                                              this_report_relpath, iterpool));
359            }
360          continue;
361        }
362
363      /*** The Big Tests: ***/
364      if (ths->status == svn_wc__db_status_server_excluded
365          || ths->status == svn_wc__db_status_not_present)
366        {
367          /* If the entry is 'absent' or 'not-present', make sure the server
368             knows it's gone...
369             ...unless we're reporting everything, in which case we're
370             going to report it missing later anyway.
371
372             This instructs the server to send it back to us, if it is
373             now available (an addition after a not-present state), or if
374             it is now authorized (change in authz for the absent item).  */
375          if (! report_everything)
376            SVN_ERR(reporter->delete_path(report_baton, this_report_relpath,
377                                          iterpool));
378          continue;
379        }
380
381      /* Is the entry NOT on the disk? We may be able to restore it.  */
382      if (restore_files
383          && svn_hash_gets(dirents, child) == NULL)
384        {
385          svn_wc__db_status_t wrk_status;
386          svn_node_kind_t wrk_kind;
387          const svn_checksum_t *checksum;
388          svn_boolean_t conflicted;
389
390          SVN_ERR(svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL,
391                                       NULL, NULL, NULL, NULL, NULL, NULL,
392                                       &checksum, NULL, NULL, NULL, NULL, NULL,
393                                       NULL, NULL, NULL, NULL, &conflicted,
394                                       NULL, NULL, NULL, NULL, NULL, NULL,
395                                       db, this_abspath, iterpool, iterpool));
396
397          if ((wrk_status == svn_wc__db_status_normal
398               || wrk_status == svn_wc__db_status_added
399               || wrk_status == svn_wc__db_status_incomplete)
400              && (wrk_kind == svn_node_dir || checksum))
401            {
402              svn_node_kind_t dirent_kind;
403
404              /* It is possible on a case insensitive system that the
405                 entry is not really missing, but just cased incorrectly.
406                 In this case we can't overwrite it with the pristine
407                 version */
408              SVN_ERR(svn_io_check_path(this_abspath, &dirent_kind, iterpool));
409
410              if (dirent_kind == svn_node_none)
411                {
412                  SVN_ERR(restore_node(db, this_abspath, wrk_kind,
413                                       conflicted, use_commit_times,
414                                       cancel_func, cancel_baton,
415                                       notify_func, notify_baton, iterpool));
416                }
417            }
418        }
419
420      /* And finally prepare for reporting */
421      if (!ths->repos_relpath)
422        {
423          ths->repos_relpath = svn_relpath_join(dir_repos_relpath, child,
424                                                iterpool);
425        }
426      else
427        {
428          const char *childname
429            = svn_relpath_skip_ancestor(dir_repos_relpath, ths->repos_relpath);
430
431          if (childname == NULL || strcmp(childname, child) != 0)
432            {
433              this_switched = TRUE;
434            }
435        }
436
437      /* Tweak THIS_DEPTH to a useful value.  */
438      if (ths->depth == svn_depth_unknown)
439        ths->depth = svn_depth_infinity;
440
441      /*** Files ***/
442      if (ths->kind == svn_node_file
443          || ths->kind == svn_node_symlink)
444        {
445          if (report_everything)
446            {
447              /* Report the file unconditionally, one way or another. */
448              if (this_switched)
449                SVN_ERR(reporter->link_path(report_baton,
450                                            this_report_relpath,
451                                            svn_path_url_add_component2(
452                                                dir_repos_root,
453                                                ths->repos_relpath, iterpool),
454                                            ths->revnum,
455                                            ths->depth,
456                                            FALSE,
457                                            ths->lock ? ths->lock->token : NULL,
458                                            iterpool));
459              else
460                SVN_ERR(reporter->set_path(report_baton,
461                                           this_report_relpath,
462                                           ths->revnum,
463                                           ths->depth,
464                                           FALSE,
465                                           ths->lock ? ths->lock->token : NULL,
466                                           iterpool));
467            }
468
469          /* Possibly report a disjoint URL ... */
470          else if (this_switched)
471            SVN_ERR(reporter->link_path(report_baton,
472                                        this_report_relpath,
473                                        svn_path_url_add_component2(
474                                                dir_repos_root,
475                                                ths->repos_relpath, iterpool),
476                                        ths->revnum,
477                                        ths->depth,
478                                        FALSE,
479                                        ths->lock ? ths->lock->token : NULL,
480                                        iterpool));
481          /* ... or perhaps just a differing revision or lock token,
482             or the mere presence of the file in a depth-empty dir. */
483          else if (ths->revnum != dir_rev
484                   || ths->lock
485                   || dir_depth == svn_depth_empty)
486            SVN_ERR(reporter->set_path(report_baton,
487                                       this_report_relpath,
488                                       ths->revnum,
489                                       ths->depth,
490                                       FALSE,
491                                       ths->lock ? ths->lock->token : NULL,
492                                       iterpool));
493        } /* end file case */
494
495      /*** Directories (in recursive mode) ***/
496      else if (ths->kind == svn_node_dir
497               && (depth > svn_depth_files
498                   || depth == svn_depth_unknown))
499        {
500          svn_boolean_t is_incomplete;
501          svn_boolean_t start_empty;
502          svn_depth_t report_depth = ths->depth;
503
504          is_incomplete = (ths->status == svn_wc__db_status_incomplete);
505          start_empty = is_incomplete;
506
507          if (!SVN_DEPTH_IS_RECURSIVE(depth))
508            report_depth = svn_depth_empty;
509
510          /* When a <= 1.6 working copy is upgraded without some of its
511             subdirectories we miss some information in the database. If we
512             report the revision as -1, the update editor will receive an
513             add_directory() while it still knows the directory.
514
515             This would raise strange tree conflicts and probably assertions
516             as it would a BASE vs BASE conflict */
517          if (is_incomplete && !SVN_IS_VALID_REVNUM(ths->revnum))
518            ths->revnum = dir_rev;
519
520          if (depth_compatibility_trick
521              && ths->depth <= svn_depth_files
522              && depth > ths->depth)
523            {
524              start_empty = TRUE;
525            }
526
527          if (report_everything)
528            {
529              /* Report the dir unconditionally, one way or another... */
530              if (this_switched)
531                SVN_ERR(reporter->link_path(report_baton,
532                                            this_report_relpath,
533                                            svn_path_url_add_component2(
534                                                dir_repos_root,
535                                                ths->repos_relpath, iterpool),
536                                            ths->revnum,
537                                            report_depth,
538                                            start_empty,
539                                            ths->lock ? ths->lock->token
540                                                      : NULL,
541                                            iterpool));
542              else
543                SVN_ERR(reporter->set_path(report_baton,
544                                           this_report_relpath,
545                                           ths->revnum,
546                                           report_depth,
547                                           start_empty,
548                                           ths->lock ? ths->lock->token : NULL,
549                                           iterpool));
550            }
551          else if (this_switched)
552            {
553              /* ...or possibly report a disjoint URL ... */
554              SVN_ERR(reporter->link_path(report_baton,
555                                          this_report_relpath,
556                                          svn_path_url_add_component2(
557                                              dir_repos_root,
558                                              ths->repos_relpath, iterpool),
559                                          ths->revnum,
560                                          report_depth,
561                                          start_empty,
562                                          ths->lock ? ths->lock->token : NULL,
563                                          iterpool));
564            }
565          else if (ths->revnum != dir_rev
566                   || ths->lock
567                   || is_incomplete
568                   || dir_depth == svn_depth_empty
569                   || dir_depth == svn_depth_files
570                   || (dir_depth == svn_depth_immediates
571                       && ths->depth != svn_depth_empty)
572                   || (ths->depth < svn_depth_infinity
573                       && SVN_DEPTH_IS_RECURSIVE(depth)))
574            {
575              /* ... or perhaps just a differing revision, lock token,
576                 incomplete subdir, the mere presence of the directory
577                 in a depth-empty or depth-files dir, or if the parent
578                 dir is at depth-immediates but the child is not at
579                 depth-empty.  Also describe shallow subdirs if we are
580                 trying to set depth to infinity. */
581              SVN_ERR(reporter->set_path(report_baton,
582                                         this_report_relpath,
583                                         ths->revnum,
584                                         report_depth,
585                                         start_empty,
586                                         ths->lock ? ths->lock->token : NULL,
587                                         iterpool));
588            }
589
590          /* Finally, recurse if necessary and appropriate. */
591          if (SVN_DEPTH_IS_RECURSIVE(depth))
592            {
593              const char *repos_relpath = ths->repos_relpath;
594
595              if (repos_relpath == NULL)
596                {
597                  repos_relpath = svn_relpath_join(dir_repos_relpath, child,
598                                                   iterpool);
599                }
600
601              SVN_ERR(report_revisions_and_depths(db,
602                                                  this_abspath,
603                                                  this_report_relpath,
604                                                  ths->revnum,
605                                                  repos_relpath,
606                                                  dir_repos_root,
607                                                  ths->depth,
608                                                  reporter, report_baton,
609                                                  restore_files, depth,
610                                                  honor_depth_exclude,
611                                                  depth_compatibility_trick,
612                                                  start_empty,
613                                                  use_commit_times,
614                                                  cancel_func, cancel_baton,
615                                                  notify_func, notify_baton,
616                                                  iterpool));
617            }
618        } /* end directory case */
619    } /* end main entries loop */
620
621  /* We're done examining this dir's entries, so free everything. */
622  svn_pool_destroy(iterpool);
623
624  return SVN_NO_ERROR;
625}
626
627
628/*------------------------------------------------------------------*/
629/*** Public Interfaces ***/
630
631
632svn_error_t *
633svn_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 */
878struct 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/* */
889static svn_error_t *
890read_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/* */
900static svn_error_t *
901close_handler_copy(void *baton)
902{
903  struct copying_stream_baton *btn = baton;
904
905  SVN_ERR(svn_stream_close(btn->target));
906  return svn_stream_close(btn->source);
907}
908
909/* Implements svn_stream_seek_fn_t */
910static svn_error_t *
911seek_handler_copy(void *baton, const svn_stream_mark_t *mark)
912{
913  struct copying_stream_baton *btn = baton;
914
915  /* Only reset support. */
916  if (mark)
917    {
918      return svn_error_create(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED,
919                              NULL, NULL);
920    }
921  else
922    {
923      SVN_ERR(svn_stream_reset(btn->source));
924      SVN_ERR(svn_stream_reset(btn->target));
925    }
926
927  return SVN_NO_ERROR;
928}
929
930
931/* Return a stream - allocated in POOL - which reads its input
932 * from SOURCE and, while returning that to the caller, at the
933 * same time writes that to TARGET.
934 */
935static svn_stream_t *
936copying_stream(svn_stream_t *source,
937               svn_stream_t *target,
938               apr_pool_t *pool)
939{
940  struct copying_stream_baton *baton;
941  svn_stream_t *stream;
942
943  baton = apr_palloc(pool, sizeof (*baton));
944  baton->source = source;
945  baton->target = target;
946
947  stream = svn_stream_create(baton, pool);
948  svn_stream_set_read2(stream, NULL /* only full read support */,
949                       read_handler_copy);
950  svn_stream_set_close(stream, close_handler_copy);
951
952  if (svn_stream_supports_reset(source) && svn_stream_supports_reset(target))
953    {
954      svn_stream_set_seek(stream, seek_handler_copy);
955    }
956
957  return stream;
958}
959
960
961/* Set *STREAM to a stream from which the caller can read the pristine text
962 * of the working version of the file at LOCAL_ABSPATH.  If the working
963 * version of LOCAL_ABSPATH has no pristine text because it is locally
964 * added, set *STREAM to an empty stream.  If the working version of
965 * LOCAL_ABSPATH is not a file, return an error.
966 *
967 * Set *EXPECTED_MD5_CHECKSUM to the recorded MD5 checksum.
968 *
969 * Arrange for the actual checksum of the text to be calculated and written
970 * into *ACTUAL_MD5_CHECKSUM when the stream is read.
971 */
972static svn_error_t *
973read_and_checksum_pristine_text(svn_stream_t **stream,
974                                const svn_checksum_t **expected_md5_checksum,
975                                svn_checksum_t **actual_md5_checksum,
976                                svn_wc__db_t *db,
977                                const char *local_abspath,
978                                apr_pool_t *result_pool,
979                                apr_pool_t *scratch_pool)
980{
981  svn_stream_t *base_stream;
982
983  SVN_ERR(svn_wc__get_pristine_contents(&base_stream, NULL, db, local_abspath,
984                                        result_pool, scratch_pool));
985  if (base_stream == NULL)
986    {
987      base_stream = svn_stream_empty(result_pool);
988      *expected_md5_checksum = NULL;
989      *actual_md5_checksum = NULL;
990    }
991  else
992    {
993      const svn_checksum_t *expected_md5;
994
995      SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
996                                   NULL, NULL, NULL, NULL, &expected_md5,
997                                   NULL, NULL, NULL, NULL, NULL, NULL,
998                                   NULL, NULL, NULL, NULL, NULL, NULL,
999                                   NULL, NULL, NULL, NULL,
1000                                   db, local_abspath,
1001                                   result_pool, scratch_pool));
1002      if (expected_md5 == NULL)
1003        return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
1004                                 _("Pristine checksum for file '%s' is missing"),
1005                                 svn_dirent_local_style(local_abspath,
1006                                                        scratch_pool));
1007      if (expected_md5->kind != svn_checksum_md5)
1008        SVN_ERR(svn_wc__db_pristine_get_md5(&expected_md5, db, local_abspath,
1009                                            expected_md5,
1010                                            result_pool, scratch_pool));
1011      *expected_md5_checksum = expected_md5;
1012
1013      /* Arrange to set ACTUAL_MD5_CHECKSUM to the MD5 of what is *actually*
1014         found when the base stream is read. */
1015      base_stream = svn_stream_checksummed2(base_stream, actual_md5_checksum,
1016                                            NULL, svn_checksum_md5, TRUE,
1017                                            result_pool);
1018    }
1019
1020  *stream = base_stream;
1021  return SVN_NO_ERROR;
1022}
1023
1024typedef struct open_txdelta_stream_baton_t
1025{
1026  svn_boolean_t need_reset;
1027  svn_stream_t *base_stream;
1028  svn_stream_t *local_stream;
1029} open_txdelta_stream_baton_t;
1030
1031/* Implements svn_txdelta_stream_open_func_t */
1032static svn_error_t *
1033open_txdelta_stream(svn_txdelta_stream_t **txdelta_stream_p,
1034                    void *baton,
1035                    apr_pool_t *result_pool,
1036                    apr_pool_t *scratch_pool)
1037{
1038  open_txdelta_stream_baton_t *b = baton;
1039
1040  if (b->need_reset)
1041    {
1042      /* Under rare circumstances, we can be restarted and would need to
1043       * supply the delta stream again.  In this case, reset both streams. */
1044      SVN_ERR(svn_stream_reset(b->base_stream));
1045      SVN_ERR(svn_stream_reset(b->local_stream));
1046    }
1047
1048  svn_txdelta2(txdelta_stream_p, b->base_stream, b->local_stream,
1049               FALSE, result_pool);
1050  b->need_reset = TRUE;
1051  return SVN_NO_ERROR;
1052}
1053
1054svn_error_t *
1055svn_wc__internal_transmit_text_deltas(svn_stream_t *tempstream,
1056                                      const svn_checksum_t **new_text_base_md5_checksum,
1057                                      const svn_checksum_t **new_text_base_sha1_checksum,
1058                                      svn_wc__db_t *db,
1059                                      const char *local_abspath,
1060                                      svn_boolean_t fulltext,
1061                                      const svn_delta_editor_t *editor,
1062                                      void *file_baton,
1063                                      apr_pool_t *result_pool,
1064                                      apr_pool_t *scratch_pool)
1065{
1066  const svn_checksum_t *expected_md5_checksum;  /* recorded MD5 of BASE_S. */
1067  svn_checksum_t *verify_checksum;  /* calc'd MD5 of BASE_STREAM */
1068  svn_checksum_t *local_md5_checksum;  /* calc'd MD5 of LOCAL_STREAM */
1069  svn_checksum_t *local_sha1_checksum;  /* calc'd SHA1 of LOCAL_STREAM */
1070  svn_wc__db_install_data_t *install_data = NULL;
1071  svn_error_t *err;
1072  svn_error_t *err2;
1073  svn_stream_t *base_stream;  /* delta source */
1074  svn_stream_t *local_stream;  /* delta target: LOCAL_ABSPATH transl. to NF */
1075
1076  /* Translated input */
1077  SVN_ERR(svn_wc__internal_translated_stream(&local_stream, db,
1078                                             local_abspath, local_abspath,
1079                                             SVN_WC_TRANSLATE_TO_NF,
1080                                             scratch_pool, scratch_pool));
1081
1082  /* If the caller wants a copy of the working file translated to
1083   * repository-normal form, make the copy by tee-ing the TEMPSTREAM.
1084   * This is only needed for the 1.6 API.  Even when using the 1.6 API
1085   * this temporary file is not used by the functions that would have used
1086   * it when using the 1.6 code.  It's possible that 3rd party users (if
1087   * there are any) might expect this file to be a text-base. */
1088  if (tempstream)
1089    {
1090      /* Wrap the translated stream with a new stream that writes the
1091         translated contents into the new text base file as we read from it.
1092         Note that the new text base file will be closed when the new stream
1093         is closed. */
1094      local_stream = copying_stream(local_stream, tempstream, scratch_pool);
1095    }
1096  if (new_text_base_sha1_checksum)
1097    {
1098      svn_stream_t *new_pristine_stream;
1099
1100      SVN_ERR(svn_wc__db_pristine_prepare_install(&new_pristine_stream,
1101                                                  &install_data,
1102                                                  &local_sha1_checksum, NULL,
1103                                                  db, local_abspath,
1104                                                  scratch_pool, scratch_pool));
1105      local_stream = copying_stream(local_stream, new_pristine_stream,
1106                                    scratch_pool);
1107    }
1108
1109  /* If sending a full text is requested, or if there is no pristine text
1110   * (e.g. the node is locally added), then set BASE_STREAM to an empty
1111   * stream and leave EXPECTED_MD5_CHECKSUM and VERIFY_CHECKSUM as NULL.
1112   *
1113   * Otherwise, set BASE_STREAM to a stream providing the base (source) text
1114   * for the delta, set EXPECTED_MD5_CHECKSUM to its stored MD5 checksum,
1115   * and arrange for its VERIFY_CHECKSUM to be calculated later. */
1116  if (! fulltext)
1117    {
1118      /* We will be computing a delta against the pristine contents */
1119      /* We need the expected checksum to be an MD-5 checksum rather than a
1120       * SHA-1 because we want to pass it to apply_textdelta(). */
1121      SVN_ERR(read_and_checksum_pristine_text(&base_stream,
1122                                              &expected_md5_checksum,
1123                                              &verify_checksum,
1124                                              db, local_abspath,
1125                                              scratch_pool, scratch_pool));
1126    }
1127  else
1128    {
1129      /* Send a fulltext. */
1130      base_stream = svn_stream_empty(scratch_pool);
1131      expected_md5_checksum = NULL;
1132      verify_checksum = NULL;
1133    }
1134
1135  /* Arrange the stream to calculate the resulting MD5. */
1136  local_stream = svn_stream_checksummed2(local_stream, &local_md5_checksum,
1137                                         NULL, svn_checksum_md5, TRUE,
1138                                         scratch_pool);
1139
1140  /* Tell the editor to apply a textdelta stream to the file baton. */
1141  {
1142    open_txdelta_stream_baton_t baton = { 0 };
1143
1144    /* apply_textdelta_stream() is working against a base with this checksum */
1145    const char *base_digest_hex = NULL;
1146
1147    if (expected_md5_checksum)
1148      /* ### Why '..._display()'?  expected_md5_checksum should never be all-
1149       * zero, but if it is, we would want to pass NULL not an all-zero
1150       * digest to apply_textdelta_stream(), wouldn't we? */
1151      base_digest_hex = svn_checksum_to_cstring_display(expected_md5_checksum,
1152                                                        scratch_pool);
1153
1154    baton.need_reset = FALSE;
1155    baton.base_stream = svn_stream_disown(base_stream, scratch_pool);
1156    baton.local_stream = svn_stream_disown(local_stream, scratch_pool);
1157    err = editor->apply_textdelta_stream(editor, file_baton, base_digest_hex,
1158                                         open_txdelta_stream, &baton,
1159                                         scratch_pool);
1160  }
1161
1162  /* Close the two streams to force writing the digest */
1163  err2 = svn_stream_close(base_stream);
1164  if (err2)
1165    {
1166      /* Set verify_checksum to NULL if svn_stream_close() returns error
1167         because checksum will be uninitialized in this case. */
1168      verify_checksum = NULL;
1169      err = svn_error_compose_create(err, err2);
1170    }
1171
1172  err = svn_error_compose_create(err, svn_stream_close(local_stream));
1173
1174  /* If we have an error, it may be caused by a corrupt text base,
1175     so check the checksum. */
1176  if (expected_md5_checksum && verify_checksum
1177      && !svn_checksum_match(expected_md5_checksum, verify_checksum))
1178    {
1179      /* The entry checksum does not match the actual text
1180         base checksum.  Extreme badness. Of course,
1181         theoretically we could just switch to
1182         fulltext transmission here, and everything would
1183         work fine; after all, we're going to replace the
1184         text base with a new one in a moment anyway, and
1185         we'd fix the checksum then.  But it's better to
1186         error out.  People should know that their text
1187         bases are getting corrupted, so they can
1188         investigate.  Other commands could be affected,
1189         too, such as `svn diff'.  */
1190
1191      err = svn_error_compose_create(
1192              svn_checksum_mismatch_err(expected_md5_checksum, verify_checksum,
1193                            scratch_pool,
1194                            _("Checksum mismatch for text base of '%s'"),
1195                            svn_dirent_local_style(local_abspath,
1196                                                   scratch_pool)),
1197              err);
1198
1199      return svn_error_create(SVN_ERR_WC_CORRUPT_TEXT_BASE, err, NULL);
1200    }
1201
1202  /* Now, handle that delta transmission error if any, so we can stop
1203     thinking about it after this point. */
1204  SVN_ERR_W(err, apr_psprintf(scratch_pool,
1205                              _("While preparing '%s' for commit"),
1206                              svn_dirent_local_style(local_abspath,
1207                                                     scratch_pool)));
1208
1209  if (new_text_base_md5_checksum)
1210    *new_text_base_md5_checksum = svn_checksum_dup(local_md5_checksum,
1211                                                   result_pool);
1212  if (new_text_base_sha1_checksum)
1213    {
1214      SVN_ERR(svn_wc__db_pristine_install(install_data,
1215                                          local_sha1_checksum,
1216                                          local_md5_checksum,
1217                                          scratch_pool));
1218      *new_text_base_sha1_checksum = svn_checksum_dup(local_sha1_checksum,
1219                                                      result_pool);
1220    }
1221
1222  /* Close the file baton, and get outta here. */
1223  return svn_error_trace(
1224             editor->close_file(file_baton,
1225                                svn_checksum_to_cstring(local_md5_checksum,
1226                                                        scratch_pool),
1227                                scratch_pool));
1228}
1229
1230svn_error_t *
1231svn_wc_transmit_text_deltas3(const svn_checksum_t **new_text_base_md5_checksum,
1232                             const svn_checksum_t **new_text_base_sha1_checksum,
1233                             svn_wc_context_t *wc_ctx,
1234                             const char *local_abspath,
1235                             svn_boolean_t fulltext,
1236                             const svn_delta_editor_t *editor,
1237                             void *file_baton,
1238                             apr_pool_t *result_pool,
1239                             apr_pool_t *scratch_pool)
1240{
1241  return svn_wc__internal_transmit_text_deltas(NULL,
1242                                               new_text_base_md5_checksum,
1243                                               new_text_base_sha1_checksum,
1244                                               wc_ctx->db, local_abspath,
1245                                               fulltext, editor,
1246                                               file_baton, result_pool,
1247                                               scratch_pool);
1248}
1249
1250svn_error_t *
1251svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db,
1252                                     const char *local_abspath,
1253                                     const svn_delta_editor_t *editor,
1254                                     void *baton,
1255                                     apr_pool_t *scratch_pool)
1256{
1257  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1258  int i;
1259  apr_array_header_t *propmods;
1260  svn_node_kind_t kind;
1261
1262  SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath,
1263                               FALSE /* allow_missing */,
1264                               FALSE /* show_deleted */,
1265                               FALSE /* show_hidden */,
1266                               iterpool));
1267
1268  if (kind == svn_node_none)
1269    return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1270                             _("The node '%s' was not found."),
1271                             svn_dirent_local_style(local_abspath, iterpool));
1272
1273  /* Get an array of local changes by comparing the hashes. */
1274  SVN_ERR(svn_wc__internal_propdiff(&propmods, NULL, db, local_abspath,
1275                                    scratch_pool, iterpool));
1276
1277  /* Apply each local change to the baton */
1278  for (i = 0; i < propmods->nelts; i++)
1279    {
1280      const svn_prop_t *p = &APR_ARRAY_IDX(propmods, i, svn_prop_t);
1281
1282      svn_pool_clear(iterpool);
1283
1284      if (kind == svn_node_file)
1285        SVN_ERR(editor->change_file_prop(baton, p->name, p->value,
1286                                         iterpool));
1287      else
1288        SVN_ERR(editor->change_dir_prop(baton, p->name, p->value,
1289                                        iterpool));
1290    }
1291
1292  svn_pool_destroy(iterpool);
1293  return SVN_NO_ERROR;
1294}
1295
1296svn_error_t *
1297svn_wc_transmit_prop_deltas2(svn_wc_context_t *wc_ctx,
1298                             const char *local_abspath,
1299                             const svn_delta_editor_t *editor,
1300                             void *baton,
1301                             apr_pool_t *scratch_pool)
1302{
1303  return svn_wc__internal_transmit_prop_deltas(wc_ctx->db, local_abspath,
1304                                               editor, baton, scratch_pool);
1305}
1306