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