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