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