1 /*
2 * externals.c : routines dealing with (file) externals in the working copy
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 #include <stdlib.h>
27 #include <string.h>
28
29 #include <apr_pools.h>
30 #include <apr_hash.h>
31 #include <apr_tables.h>
32 #include <apr_general.h>
33 #include <apr_uri.h>
34
35 #include "svn_dirent_uri.h"
36 #include "svn_path.h"
37 #include "svn_error.h"
38 #include "svn_hash.h"
39 #include "svn_io.h"
40 #include "svn_pools.h"
41 #include "svn_props.h"
42 #include "svn_string.h"
43 #include "svn_time.h"
44 #include "svn_types.h"
45 #include "svn_wc.h"
46
47 #include "private/svn_skel.h"
48 #include "private/svn_subr_private.h"
49
50 #include "wc.h"
51 #include "adm_files.h"
52 #include "props.h"
53 #include "translate.h"
54 #include "workqueue.h"
55 #include "conflicts.h"
56
57 #include "svn_private_config.h"
58
59 /** Externals **/
60
61 /*
62 * Look for either
63 *
64 * -r N
65 * -rN
66 *
67 * in the LINE_PARTS array and update the revision field in ITEM with
68 * the revision if the revision is found. Set REV_IDX to the index in
69 * LINE_PARTS where the revision specification starts. Remove from
70 * LINE_PARTS the element(s) that specify the revision.
71 * Set REV_STR to the element that specifies the revision.
72 * PARENT_DIRECTORY_DISPLAY and LINE are given to return a nice error
73 * string.
74 *
75 * If this function returns successfully, then LINE_PARTS will have
76 * only two elements in it.
77 */
78 static svn_error_t *
find_and_remove_externals_revision(int * rev_idx,const char ** rev_str,const char ** line_parts,int num_line_parts,svn_wc_external_item2_t * item,const char * parent_directory_display,const char * line,apr_pool_t * pool)79 find_and_remove_externals_revision(int *rev_idx,
80 const char **rev_str,
81 const char **line_parts,
82 int num_line_parts,
83 svn_wc_external_item2_t *item,
84 const char *parent_directory_display,
85 const char *line,
86 apr_pool_t *pool)
87 {
88 int i;
89
90 for (i = 0; i < 2; ++i)
91 {
92 const char *token = line_parts[i];
93
94 if (token[0] == '-' && token[1] == 'r')
95 {
96 svn_opt_revision_t end_revision = { svn_opt_revision_unspecified };
97 const char *digits_ptr;
98 int shift_count;
99 int j;
100
101 *rev_idx = i;
102
103 if (token[2] == '\0')
104 {
105 /* There must be a total of four elements in the line if
106 -r N is used. */
107 if (num_line_parts != 4)
108 goto parse_error;
109
110 shift_count = 2;
111 digits_ptr = line_parts[i+1];
112 }
113 else
114 {
115 /* There must be a total of three elements in the line
116 if -rN is used. */
117 if (num_line_parts != 3)
118 goto parse_error;
119
120 shift_count = 1;
121 digits_ptr = token+2;
122 }
123
124 if (svn_opt_parse_revision(&item->revision,
125 &end_revision,
126 digits_ptr, pool) != 0)
127 goto parse_error;
128 /* We want a single revision, not a range. */
129 if (end_revision.kind != svn_opt_revision_unspecified)
130 goto parse_error;
131 /* Allow only numbers and dates, not keywords. */
132 if (item->revision.kind != svn_opt_revision_number
133 && item->revision.kind != svn_opt_revision_date)
134 goto parse_error;
135
136 /* Shift any line elements past the revision specification
137 down over the revision specification. */
138 for (j = i; j < num_line_parts-shift_count; ++j)
139 line_parts[j] = line_parts[j+shift_count];
140 line_parts[num_line_parts-shift_count] = NULL;
141
142 *rev_str = apr_psprintf(pool, "-r%s", digits_ptr);
143
144 /* Found the revision, so leave the function immediately, do
145 * not continue looking for additional revisions. */
146 return SVN_NO_ERROR;
147 }
148 }
149
150 /* No revision was found, so there must be exactly two items in the
151 line array. */
152 if (num_line_parts == 2)
153 return SVN_NO_ERROR;
154
155 parse_error:
156 return svn_error_createf
157 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
158 _("Error parsing %s property on '%s': '%s'"),
159 SVN_PROP_EXTERNALS,
160 parent_directory_display,
161 line);
162 }
163
164 svn_error_t *
svn_wc__parse_externals_description(apr_array_header_t ** externals_p,apr_array_header_t ** parser_infos_p,const char * defining_directory,const char * desc,svn_boolean_t canonicalize_url,apr_pool_t * pool)165 svn_wc__parse_externals_description(apr_array_header_t **externals_p,
166 apr_array_header_t **parser_infos_p,
167 const char *defining_directory,
168 const char *desc,
169 svn_boolean_t canonicalize_url,
170 apr_pool_t *pool)
171 {
172 int i;
173 apr_array_header_t *externals = NULL;
174 apr_array_header_t *parser_infos = NULL;
175 apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool);
176 const char *defining_directory_display = svn_path_is_url(defining_directory) ?
177 defining_directory : svn_dirent_local_style(defining_directory, pool);
178
179 /* If an error occurs halfway through parsing, *externals_p should stay
180 * untouched. So, store the list in a local var first. */
181 if (externals_p)
182 externals = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *));
183
184 if (parser_infos_p)
185 parser_infos =
186 apr_array_make(pool, 1, sizeof(svn_wc__externals_parser_info_t *));
187
188 for (i = 0; i < lines->nelts; i++)
189 {
190 const char *line = APR_ARRAY_IDX(lines, i, const char *);
191 apr_status_t status;
192 char **line_parts;
193 int num_line_parts;
194 svn_wc_external_item2_t *item;
195 const char *token0;
196 const char *token1;
197 svn_boolean_t token0_is_url;
198 svn_boolean_t token1_is_url;
199 svn_wc__externals_parser_info_t *info = NULL;
200
201 /* Index into line_parts where the revision specification
202 started. */
203 int rev_idx = -1;
204 const char *rev_str = NULL;
205
206 if ((! line) || (line[0] == '#'))
207 continue;
208
209 /* else proceed */
210
211 status = apr_tokenize_to_argv(line, &line_parts, pool);
212 if (status)
213 return svn_error_wrap_apr(status,
214 _("Can't split line into components: '%s'"),
215 line);
216 /* Count the number of tokens. */
217 for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++)
218 ;
219
220 SVN_ERR(svn_wc_external_item2_create(&item, pool));
221 item->revision.kind = svn_opt_revision_unspecified;
222 item->peg_revision.kind = svn_opt_revision_unspecified;
223
224 if (parser_infos)
225 info = apr_pcalloc(pool, sizeof(*info));
226
227 /*
228 * There are six different formats of externals:
229 *
230 * 1) DIR URL
231 * 2) DIR -r N URL
232 * 3) DIR -rN URL
233 * 4) URL DIR
234 * 5) -r N URL DIR
235 * 6) -rN URL DIR
236 *
237 * The last three allow peg revisions in the URL.
238 *
239 * With relative URLs and no '-rN' or '-r N', there is no way to
240 * distinguish between 'DIR URL' and 'URL DIR' when URL is a
241 * relative URL like /svn/repos/trunk, so this case is taken as
242 * case 4).
243 */
244 if (num_line_parts < 2 || num_line_parts > 4)
245 return svn_error_createf
246 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
247 _("Error parsing %s property on '%s': '%s'"),
248 SVN_PROP_EXTERNALS,
249 defining_directory_display,
250 line);
251
252 /* To make it easy to check for the forms, find and remove -r N
253 or -rN from the line item array. If it is found, rev_idx
254 contains the index into line_parts where '-r' was found and
255 set item->revision to the parsed revision. */
256 /* ### ugh. stupid cast. */
257 SVN_ERR(find_and_remove_externals_revision(&rev_idx,
258 &rev_str,
259 (const char **)line_parts,
260 num_line_parts, item,
261 defining_directory_display,
262 line, pool));
263
264 token0 = line_parts[0];
265 token1 = line_parts[1];
266
267 token0_is_url = svn_path_is_url(token0);
268 token1_is_url = svn_path_is_url(token1);
269
270 if (token0_is_url && token1_is_url)
271 return svn_error_createf
272 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
273 _("Invalid %s property on '%s': "
274 "cannot use two absolute URLs ('%s' and '%s') in an external; "
275 "one must be a path where an absolute or relative URL is "
276 "checked out to"),
277 SVN_PROP_EXTERNALS, defining_directory_display, token0, token1);
278
279 if (0 == rev_idx && token1_is_url)
280 return svn_error_createf
281 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
282 _("Invalid %s property on '%s': "
283 "cannot use a URL '%s' as the target directory for an external "
284 "definition"),
285 SVN_PROP_EXTERNALS, defining_directory_display, token1);
286
287 if (1 == rev_idx && token0_is_url)
288 return svn_error_createf
289 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
290 _("Invalid %s property on '%s': "
291 "cannot use a URL '%s' as the target directory for an external "
292 "definition"),
293 SVN_PROP_EXTERNALS, defining_directory_display, token0);
294
295 /* The appearance of -r N or -rN forces the type of external.
296 If -r is at the beginning of the line or the first token is
297 an absolute URL or if the second token is not an absolute
298 URL, then the URL supports peg revisions. */
299 if (0 == rev_idx ||
300 (-1 == rev_idx && (token0_is_url || ! token1_is_url)))
301 {
302 /* The URL is passed to svn_opt_parse_path in
303 uncanonicalized form so that the scheme relative URL
304 //hostname/foo is not collapsed to a server root relative
305 URL /hostname/foo. */
306 SVN_ERR(svn_opt_parse_path(&item->peg_revision, &item->url,
307 token0, pool));
308 item->target_dir = token1;
309
310 if (info)
311 {
312 info->format = svn_wc__external_description_format_2;
313
314 if (rev_str)
315 info->rev_str = apr_pstrdup(pool, rev_str);
316
317 if (item->peg_revision.kind != svn_opt_revision_unspecified)
318 info->peg_rev_str = strrchr(token0, '@');
319 }
320 }
321 else
322 {
323 item->target_dir = token0;
324 item->url = token1;
325 item->peg_revision = item->revision;
326
327 if (info)
328 {
329 info->format = svn_wc__external_description_format_1;
330
331 if (rev_str)
332 {
333 info->rev_str = apr_pstrdup(pool, rev_str);
334 info->peg_rev_str = info->rev_str;
335 }
336 }
337 }
338
339 SVN_ERR(svn_opt_resolve_revisions(&item->peg_revision,
340 &item->revision, TRUE, FALSE,
341 pool));
342
343 item->target_dir = svn_dirent_internal_style(item->target_dir, pool);
344
345 if (item->target_dir[0] == '\0'
346 || svn_dirent_is_absolute(item->target_dir)
347 || svn_path_is_backpath_present(item->target_dir)
348 || !svn_dirent_skip_ancestor("dummy",
349 svn_dirent_join("dummy",
350 item->target_dir,
351 pool)))
352 return svn_error_createf
353 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
354 _("Invalid %s property on '%s': "
355 "target '%s' is an absolute path or involves '..'"),
356 SVN_PROP_EXTERNALS,
357 defining_directory_display,
358 item->target_dir);
359
360 if (canonicalize_url)
361 {
362 /* Uh... this is stupid. But it's consistent with what our
363 code did before we split up the relpath/dirent/uri APIs.
364 Still, given this, it's no wonder that our own libraries
365 don't ask this function to canonicalize the results. */
366 if (svn_path_is_url(item->url))
367 item->url = svn_uri_canonicalize(item->url, pool);
368 else
369 item->url = svn_dirent_canonicalize(item->url, pool);
370 }
371
372 if (externals)
373 APR_ARRAY_PUSH(externals, svn_wc_external_item2_t *) = item;
374 if (parser_infos)
375 APR_ARRAY_PUSH(parser_infos, svn_wc__externals_parser_info_t *) = info;
376 }
377
378 if (externals_p)
379 *externals_p = externals;
380 if (parser_infos_p)
381 *parser_infos_p = parser_infos;
382
383 return SVN_NO_ERROR;
384 }
385
386 svn_error_t *
svn_wc_parse_externals_description3(apr_array_header_t ** externals_p,const char * defining_directory,const char * desc,svn_boolean_t canonicalize_url,apr_pool_t * pool)387 svn_wc_parse_externals_description3(apr_array_header_t **externals_p,
388 const char *defining_directory,
389 const char *desc,
390 svn_boolean_t canonicalize_url,
391 apr_pool_t *pool)
392 {
393 return svn_error_trace(svn_wc__parse_externals_description(externals_p,
394 NULL,
395 defining_directory,
396 desc,
397 canonicalize_url,
398 pool));
399 }
400
401 svn_error_t *
svn_wc__externals_find_target_dups(apr_array_header_t ** duplicate_targets,apr_array_header_t * externals,apr_pool_t * pool,apr_pool_t * scratch_pool)402 svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets,
403 apr_array_header_t *externals,
404 apr_pool_t *pool,
405 apr_pool_t *scratch_pool)
406 {
407 int i;
408 unsigned int len;
409 unsigned int len2;
410 const char *target;
411 apr_hash_t *targets = apr_hash_make(scratch_pool);
412 apr_hash_t *targets2 = NULL;
413 *duplicate_targets = NULL;
414
415 for (i = 0; i < externals->nelts; i++)
416 {
417 target = APR_ARRAY_IDX(externals, i,
418 svn_wc_external_item2_t*)->target_dir;
419 len = apr_hash_count(targets);
420 svn_hash_sets(targets, target, "");
421 if (len == apr_hash_count(targets))
422 {
423 /* Hashtable length is unchanged. This must be a duplicate. */
424
425 /* Collapse multiple duplicates of the same target by using a second
426 * hash layer. */
427 if (! targets2)
428 targets2 = apr_hash_make(scratch_pool);
429 len2 = apr_hash_count(targets2);
430 svn_hash_sets(targets2, target, "");
431 if (len2 < apr_hash_count(targets2))
432 {
433 /* The second hash list just got bigger, i.e. this target has
434 * not been counted as duplicate before. */
435 if (! *duplicate_targets)
436 {
437 *duplicate_targets = apr_array_make(
438 pool, 1, sizeof(svn_wc_external_item2_t*));
439 }
440 APR_ARRAY_PUSH((*duplicate_targets), const char *) = target;
441 }
442 /* Else, this same target has already been recorded as a duplicate,
443 * don't count it again. */
444 }
445 }
446 return SVN_NO_ERROR;
447 }
448
449 struct edit_baton
450 {
451 apr_pool_t *pool;
452 svn_wc__db_t *db;
453
454 /* We explicitly use wri_abspath and local_abspath here, because we
455 might want to install file externals in an obstructing working copy */
456 const char *wri_abspath; /* The working defining the file external */
457 const char *local_abspath; /* The file external itself */
458 const char *name; /* The basename of the file external itself */
459
460 /* Information from the caller */
461 svn_boolean_t use_commit_times;
462 const apr_array_header_t *ext_patterns;
463 const char *diff3cmd;
464
465 const char *repos_root_url;
466 const char *repos_uuid;
467 const char *old_repos_relpath;
468 const char *new_repos_relpath;
469
470 const char *record_ancestor_abspath;
471 const char *recorded_repos_relpath;
472 svn_revnum_t recorded_peg_revision;
473 svn_revnum_t recorded_revision;
474
475 /* Introducing a new file external */
476 svn_boolean_t added;
477
478 svn_wc_conflict_resolver_func2_t conflict_func;
479 void *conflict_baton;
480 svn_cancel_func_t cancel_func;
481 void *cancel_baton;
482 svn_wc_notify_func2_t notify_func;
483 void *notify_baton;
484
485 svn_revnum_t *target_revision;
486
487 /* What was there before the update */
488 svn_revnum_t original_revision;
489 const svn_checksum_t *original_checksum;
490
491 /* What we are installing now */
492 svn_wc__db_install_data_t *install_data;
493 svn_checksum_t *new_sha1_checksum;
494 svn_checksum_t *new_md5_checksum;
495
496 /* List of incoming propchanges */
497 apr_array_header_t *propchanges;
498
499 /* Array of svn_prop_inherited_item_t * structures representing the
500 properties inherited by the base node at LOCAL_ABSPATH. */
501 apr_array_header_t *iprops;
502
503 /* The last change information */
504 svn_revnum_t changed_rev;
505 apr_time_t changed_date;
506 const char *changed_author;
507
508 svn_boolean_t had_props;
509
510 svn_boolean_t file_closed;
511 };
512
513 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
514 static svn_error_t *
set_target_revision(void * edit_baton,svn_revnum_t target_revision,apr_pool_t * pool)515 set_target_revision(void *edit_baton,
516 svn_revnum_t target_revision,
517 apr_pool_t *pool)
518 {
519 struct edit_baton *eb = edit_baton;
520
521 *eb->target_revision = target_revision;
522
523 return SVN_NO_ERROR;
524 }
525
526 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
527 static svn_error_t *
open_root(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * dir_pool,void ** root_baton)528 open_root(void *edit_baton,
529 svn_revnum_t base_revision,
530 apr_pool_t *dir_pool,
531 void **root_baton)
532 {
533 *root_baton = edit_baton;
534 return SVN_NO_ERROR;
535 }
536
537 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
538 static svn_error_t *
add_file(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * file_pool,void ** file_baton)539 add_file(const char *path,
540 void *parent_baton,
541 const char *copyfrom_path,
542 svn_revnum_t copyfrom_revision,
543 apr_pool_t *file_pool,
544 void **file_baton)
545 {
546 struct edit_baton *eb = parent_baton;
547 if (strcmp(path, eb->name))
548 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
549 _("This editor can only update '%s'"),
550 svn_dirent_local_style(eb->local_abspath,
551 file_pool));
552
553 *file_baton = eb;
554 eb->original_revision = SVN_INVALID_REVNUM;
555 eb->added = TRUE;
556
557 return SVN_NO_ERROR;
558 }
559
560 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
561 static svn_error_t *
open_file(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * file_pool,void ** file_baton)562 open_file(const char *path,
563 void *parent_baton,
564 svn_revnum_t base_revision,
565 apr_pool_t *file_pool,
566 void **file_baton)
567 {
568 struct edit_baton *eb = parent_baton;
569 svn_node_kind_t kind;
570 if (strcmp(path, eb->name))
571 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
572 _("This editor can only update '%s'"),
573 svn_dirent_local_style(eb->local_abspath,
574 file_pool));
575
576 *file_baton = eb;
577 SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision,
578 &eb->old_repos_relpath, NULL, NULL,
579 &eb->changed_rev,
580 &eb->changed_date, &eb->changed_author,
581 NULL, &eb->original_checksum, NULL, NULL,
582 &eb->had_props, NULL, NULL,
583 eb->db, eb->local_abspath,
584 eb->pool, file_pool));
585
586 if (kind != svn_node_file)
587 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
588 _("Node '%s' is no existing file external"),
589 svn_dirent_local_style(eb->local_abspath,
590 file_pool));
591 return SVN_NO_ERROR;
592 }
593
594 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
595 static svn_error_t *
apply_textdelta(void * file_baton,const char * base_checksum_digest,apr_pool_t * pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)596 apply_textdelta(void *file_baton,
597 const char *base_checksum_digest,
598 apr_pool_t *pool,
599 svn_txdelta_window_handler_t *handler,
600 void **handler_baton)
601 {
602 struct edit_baton *eb = file_baton;
603 svn_stream_t *src_stream;
604 svn_stream_t *dest_stream;
605
606 if (eb->original_checksum)
607 {
608 if (base_checksum_digest)
609 {
610 svn_checksum_t *expected_checksum;
611 const svn_checksum_t *original_md5;
612
613 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
614 base_checksum_digest, pool));
615
616 if (eb->original_checksum->kind != svn_checksum_md5)
617 SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5,
618 eb->db, eb->wri_abspath,
619 eb->original_checksum,
620 pool, pool));
621 else
622 original_md5 = eb->original_checksum;
623
624 if (!svn_checksum_match(expected_checksum, original_md5))
625 return svn_error_trace(svn_checksum_mismatch_err(
626 expected_checksum,
627 original_md5,
628 pool,
629 _("Base checksum mismatch for '%s'"),
630 svn_dirent_local_style(eb->local_abspath,
631 pool)));
632 }
633
634 SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db,
635 eb->wri_abspath, eb->original_checksum,
636 pool, pool));
637 }
638 else
639 src_stream = svn_stream_empty(pool);
640
641 SVN_ERR(svn_wc__db_pristine_prepare_install(&dest_stream,
642 &eb->install_data,
643 &eb->new_sha1_checksum,
644 &eb->new_md5_checksum,
645 eb->db, eb->wri_abspath,
646 eb->pool, pool));
647
648 svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool,
649 handler, handler_baton);
650
651 return SVN_NO_ERROR;
652 }
653
654 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
655 static svn_error_t *
change_file_prop(void * file_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)656 change_file_prop(void *file_baton,
657 const char *name,
658 const svn_string_t *value,
659 apr_pool_t *pool)
660 {
661 struct edit_baton *eb = file_baton;
662 svn_prop_t *propchange;
663
664 propchange = apr_array_push(eb->propchanges);
665 propchange->name = apr_pstrdup(eb->pool, name);
666 propchange->value = svn_string_dup(value, eb->pool);
667
668 return SVN_NO_ERROR;
669 }
670
671 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
672 static svn_error_t *
close_file(void * file_baton,const char * expected_md5_digest,apr_pool_t * pool)673 close_file(void *file_baton,
674 const char *expected_md5_digest,
675 apr_pool_t *pool)
676 {
677 struct edit_baton *eb = file_baton;
678 svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
679 svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown;
680 svn_boolean_t obstructed = FALSE;
681
682 eb->file_closed = TRUE; /* We bump the revision here */
683
684 /* Check the checksum, if provided */
685 if (expected_md5_digest)
686 {
687 svn_checksum_t *expected_md5_checksum;
688 const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum;
689
690 SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
691 expected_md5_digest, pool));
692
693 if (actual_md5_checksum == NULL)
694 {
695 actual_md5_checksum = eb->original_checksum;
696
697 if (actual_md5_checksum != NULL
698 && actual_md5_checksum->kind != svn_checksum_md5)
699 {
700 SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum,
701 eb->db, eb->wri_abspath,
702 actual_md5_checksum,
703 pool, pool));
704 }
705 }
706
707 if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum))
708 return svn_checksum_mismatch_err(
709 expected_md5_checksum,
710 actual_md5_checksum, pool,
711 _("Checksum mismatch for '%s'"),
712 svn_dirent_local_style(eb->local_abspath, pool));
713 }
714
715 /* First move the file in the pristine store; this hands over the cleanup
716 behavior to the pristine store. */
717 if (eb->new_sha1_checksum)
718 {
719 SVN_ERR(svn_wc__db_pristine_install(eb->install_data,
720 eb->new_sha1_checksum,
721 eb->new_md5_checksum, pool));
722
723 eb->install_data = NULL;
724 }
725
726 /* Merge the changes */
727 {
728 svn_skel_t *all_work_items = NULL;
729 svn_skel_t *conflict_skel = NULL;
730 svn_skel_t *work_item;
731 apr_hash_t *base_props = NULL;
732 apr_hash_t *actual_props = NULL;
733 apr_hash_t *new_pristine_props = NULL;
734 apr_hash_t *new_actual_props = NULL;
735 apr_hash_t *new_dav_props = NULL;
736 const svn_checksum_t *new_checksum = NULL;
737 const svn_checksum_t *original_checksum = NULL;
738
739 svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision);
740
741 if (! added)
742 {
743 new_checksum = eb->original_checksum;
744
745 if (eb->had_props)
746 SVN_ERR(svn_wc__db_base_get_props(
747 &base_props, eb->db, eb->local_abspath, pool, pool));
748
749 SVN_ERR(svn_wc__db_read_props(
750 &actual_props, eb->db, eb->local_abspath, pool, pool));
751 }
752
753 if (!base_props)
754 base_props = apr_hash_make(pool);
755
756 if (!actual_props)
757 actual_props = apr_hash_make(pool);
758
759 if (eb->new_sha1_checksum)
760 new_checksum = eb->new_sha1_checksum;
761
762 /* Merge the properties */
763 {
764 apr_array_header_t *entry_prop_changes;
765 apr_array_header_t *dav_prop_changes;
766 apr_array_header_t *regular_prop_changes;
767 int i;
768
769 SVN_ERR(svn_categorize_props(eb->propchanges, &entry_prop_changes,
770 &dav_prop_changes, ®ular_prop_changes,
771 pool));
772
773 /* Read the entry-prop changes to update the last-changed info. */
774 for (i = 0; i < entry_prop_changes->nelts; i++)
775 {
776 const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i,
777 svn_prop_t);
778
779 if (! prop->value)
780 continue; /* authz or something */
781
782 if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR))
783 eb->changed_author = apr_pstrdup(pool, prop->value->data);
784 else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV))
785 {
786 apr_int64_t rev;
787 SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data));
788 eb->changed_rev = (svn_revnum_t)rev;
789 }
790 else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE))
791 SVN_ERR(svn_time_from_cstring(&eb->changed_date, prop->value->data,
792 pool));
793 }
794
795 /* Store the DAV-prop (aka WC-prop) changes. (This treats a list
796 * of changes as a list of new props, but we only use this when
797 * adding a new file and it's equivalent in that case.) */
798 if (dav_prop_changes->nelts > 0)
799 new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool);
800
801 /* Merge the regular prop changes. */
802 if (regular_prop_changes->nelts > 0)
803 {
804 new_pristine_props = svn_prop__patch(base_props, regular_prop_changes,
805 pool);
806 SVN_ERR(svn_wc__merge_props(&conflict_skel,
807 &prop_state,
808 &new_actual_props,
809 eb->db, eb->local_abspath,
810 NULL /* server_baseprops*/,
811 base_props,
812 actual_props,
813 regular_prop_changes,
814 pool, pool));
815 }
816 else
817 {
818 new_pristine_props = base_props;
819 new_actual_props = actual_props;
820 }
821 }
822
823 /* Merge the text */
824 if (eb->new_sha1_checksum)
825 {
826 svn_node_kind_t disk_kind;
827 svn_boolean_t install_pristine = FALSE;
828
829 SVN_ERR(svn_io_check_path(eb->local_abspath, &disk_kind, pool));
830
831 if (disk_kind == svn_node_none)
832 {
833 /* Just install the file */
834 install_pristine = TRUE;
835 content_state = svn_wc_notify_state_changed;
836 }
837 else if (disk_kind != svn_node_file
838 || (eb->added && disk_kind == svn_node_file))
839 {
840 /* The node is obstructed; we just change the DB */
841 obstructed = TRUE;
842 content_state = svn_wc_notify_state_unchanged;
843 }
844 else
845 {
846 svn_boolean_t is_mod;
847 SVN_ERR(svn_wc__internal_file_modified_p(&is_mod,
848 eb->db, eb->local_abspath,
849 FALSE, pool));
850
851 if (!is_mod)
852 {
853 install_pristine = TRUE;
854 content_state = svn_wc_notify_state_changed;
855 }
856 else
857 {
858 svn_boolean_t found_text_conflict;
859
860 /* Ok, we have to do some work to merge a local change */
861 SVN_ERR(svn_wc__perform_file_merge(&work_item,
862 &conflict_skel,
863 &found_text_conflict,
864 eb->db,
865 eb->local_abspath,
866 eb->wri_abspath,
867 new_checksum,
868 original_checksum,
869 actual_props,
870 eb->ext_patterns,
871 eb->original_revision,
872 *eb->target_revision,
873 eb->propchanges,
874 eb->diff3cmd,
875 eb->cancel_func,
876 eb->cancel_baton,
877 pool, pool));
878
879 all_work_items = svn_wc__wq_merge(all_work_items, work_item,
880 pool);
881
882 if (found_text_conflict)
883 content_state = svn_wc_notify_state_conflicted;
884 else
885 content_state = svn_wc_notify_state_merged;
886 }
887 }
888 if (install_pristine)
889 {
890 SVN_ERR(svn_wc__wq_build_file_install(&work_item, eb->db,
891 eb->local_abspath,
892 NULL,
893 eb->use_commit_times, TRUE,
894 pool, pool));
895
896 all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
897 }
898 }
899 else
900 {
901 content_state = svn_wc_notify_state_unchanged;
902 /* ### Retranslate on magic property changes, etc. */
903 }
904
905 /* Generate a conflict description, if needed */
906 if (conflict_skel)
907 {
908 SVN_ERR(svn_wc__conflict_skel_set_op_switch(
909 conflict_skel,
910 svn_wc_conflict_version_create2(
911 eb->repos_root_url,
912 eb->repos_uuid,
913 eb->old_repos_relpath,
914 eb->original_revision,
915 svn_node_file,
916 pool),
917 svn_wc_conflict_version_create2(
918 eb->repos_root_url,
919 eb->repos_uuid,
920 eb->new_repos_relpath,
921 *eb->target_revision,
922 svn_node_file,
923 pool),
924 pool, pool));
925 SVN_ERR(svn_wc__conflict_create_markers(&work_item,
926 eb->db, eb->local_abspath,
927 conflict_skel,
928 pool, pool));
929 all_work_items = svn_wc__wq_merge(all_work_items, work_item,
930 pool);
931 }
932
933 /* Install the file in the DB */
934 SVN_ERR(svn_wc__db_external_add_file(
935 eb->db,
936 eb->local_abspath,
937 eb->wri_abspath,
938 eb->new_repos_relpath,
939 eb->repos_root_url,
940 eb->repos_uuid,
941 *eb->target_revision,
942 new_pristine_props,
943 eb->iprops,
944 eb->changed_rev,
945 eb->changed_date,
946 eb->changed_author,
947 new_checksum,
948 new_dav_props,
949 eb->record_ancestor_abspath,
950 eb->recorded_repos_relpath,
951 eb->recorded_peg_revision,
952 eb->recorded_revision,
953 TRUE, new_actual_props,
954 FALSE /* keep_recorded_info */,
955 conflict_skel,
956 all_work_items,
957 pool));
958
959 /* close_edit may also update iprops for switched files, catching
960 those for which close_file is never called (e.g. an update of a
961 file external with no changes). So as a minor optimization we
962 clear the iprops so as not to set them again in close_edit. */
963 eb->iprops = NULL;
964
965 /* Run the work queue to complete the installation */
966 SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath,
967 eb->cancel_func, eb->cancel_baton, pool));
968
969 if (conflict_skel && eb->conflict_func)
970 SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db,
971 eb->local_abspath,
972 svn_node_file,
973 conflict_skel,
974 NULL /* merge_options */,
975 eb->conflict_func,
976 eb->conflict_baton,
977 eb->cancel_func,
978 eb->cancel_baton,
979 pool));
980 }
981
982 /* Notify */
983 if (eb->notify_func)
984 {
985 svn_wc_notify_action_t action;
986 svn_wc_notify_t *notify;
987
988 if (!eb->added)
989 action = obstructed ? svn_wc_notify_update_shadowed_update
990 : svn_wc_notify_update_update;
991 else
992 action = obstructed ? svn_wc_notify_update_shadowed_add
993 : svn_wc_notify_update_add;
994
995 notify = svn_wc_create_notify(eb->local_abspath, action, pool);
996 notify->kind = svn_node_file;
997
998 notify->revision = *eb->target_revision;
999 notify->prop_state = prop_state;
1000 notify->content_state = content_state;
1001
1002 notify->old_revision = eb->original_revision;
1003
1004 eb->notify_func(eb->notify_baton, notify, pool);
1005 }
1006
1007 return SVN_NO_ERROR;
1008 }
1009
1010 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
1011 static svn_error_t *
close_edit(void * edit_baton,apr_pool_t * pool)1012 close_edit(void *edit_baton,
1013 apr_pool_t *pool)
1014 {
1015 struct edit_baton *eb = edit_baton;
1016
1017 if (!eb->file_closed)
1018 {
1019 apr_hash_t *wcroot_iprops = NULL;
1020 /* The file wasn't updated, but its url or revision might have...
1021 e.g. switch between branches for relative externals.
1022
1023 Just bump the information as that is just as expensive as
1024 investigating when we should and shouldn't update it...
1025 and avoid hard to debug edge cases */
1026
1027 if (eb->iprops)
1028 {
1029 wcroot_iprops = apr_hash_make(pool);
1030 svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops);
1031 }
1032
1033 SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
1034 eb->local_abspath,
1035 svn_depth_infinity,
1036 eb->new_repos_relpath,
1037 eb->repos_root_url,
1038 eb->repos_uuid,
1039 *eb->target_revision,
1040 apr_hash_make(pool)
1041 /* exclude_relpaths */,
1042 wcroot_iprops,
1043 TRUE /* empty update */,
1044 eb->notify_func,
1045 eb->notify_baton,
1046 pool));
1047 }
1048
1049 return SVN_NO_ERROR;
1050 }
1051
1052 svn_error_t *
svn_wc__get_file_external_editor(const svn_delta_editor_t ** editor,void ** edit_baton,svn_revnum_t * target_revision,svn_wc_context_t * wc_ctx,const char * local_abspath,const char * wri_abspath,const char * url,const char * repos_root_url,const char * repos_uuid,apr_array_header_t * iprops,svn_boolean_t use_commit_times,const char * diff3_cmd,const apr_array_header_t * preserved_exts,const char * record_ancestor_abspath,const char * recorded_url,const svn_opt_revision_t * recorded_peg_rev,const svn_opt_revision_t * recorded_rev,svn_wc_conflict_resolver_func2_t conflict_func,void * conflict_baton,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1053 svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
1054 void **edit_baton,
1055 svn_revnum_t *target_revision,
1056 svn_wc_context_t *wc_ctx,
1057 const char *local_abspath,
1058 const char *wri_abspath,
1059 const char *url,
1060 const char *repos_root_url,
1061 const char *repos_uuid,
1062 apr_array_header_t *iprops,
1063 svn_boolean_t use_commit_times,
1064 const char *diff3_cmd,
1065 const apr_array_header_t *preserved_exts,
1066 const char *record_ancestor_abspath,
1067 const char *recorded_url,
1068 const svn_opt_revision_t *recorded_peg_rev,
1069 const svn_opt_revision_t *recorded_rev,
1070 svn_wc_conflict_resolver_func2_t conflict_func,
1071 void *conflict_baton,
1072 svn_cancel_func_t cancel_func,
1073 void *cancel_baton,
1074 svn_wc_notify_func2_t notify_func,
1075 void *notify_baton,
1076 apr_pool_t *result_pool,
1077 apr_pool_t *scratch_pool)
1078 {
1079 svn_wc__db_t *db = wc_ctx->db;
1080 apr_pool_t *edit_pool = result_pool;
1081 struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb));
1082 svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
1083
1084 eb->pool = edit_pool;
1085 eb->db = db;
1086 eb->local_abspath = apr_pstrdup(edit_pool, local_abspath);
1087 if (wri_abspath)
1088 eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath);
1089 else
1090 eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool);
1091 eb->name = svn_dirent_basename(eb->local_abspath, NULL);
1092 eb->target_revision = target_revision;
1093
1094 eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
1095 eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
1096 eb->new_repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url, url, edit_pool);
1097 eb->old_repos_relpath = eb->new_repos_relpath;
1098
1099 eb->original_revision = SVN_INVALID_REVNUM;
1100
1101 eb->iprops = iprops;
1102
1103 eb->use_commit_times = use_commit_times;
1104 eb->ext_patterns = preserved_exts;
1105 eb->diff3cmd = diff3_cmd;
1106
1107 eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath);
1108 eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url,
1109 edit_pool);
1110
1111 eb->changed_rev = SVN_INVALID_REVNUM;
1112
1113 if (recorded_peg_rev->kind == svn_opt_revision_number)
1114 eb->recorded_peg_revision = recorded_peg_rev->value.number;
1115 else
1116 eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1117
1118 if (recorded_rev->kind == svn_opt_revision_number)
1119 eb->recorded_revision = recorded_rev->value.number;
1120 else
1121 eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1122
1123 eb->conflict_func = conflict_func;
1124 eb->conflict_baton = conflict_baton;
1125 eb->cancel_func = cancel_func;
1126 eb->cancel_baton = cancel_baton;
1127 eb->notify_func = notify_func;
1128 eb->notify_baton = notify_baton;
1129
1130 eb->propchanges = apr_array_make(edit_pool, 1, sizeof(svn_prop_t));
1131
1132 tree_editor->open_root = open_root;
1133 tree_editor->set_target_revision = set_target_revision;
1134 tree_editor->add_file = add_file;
1135 tree_editor->open_file = open_file;
1136 tree_editor->apply_textdelta = apply_textdelta;
1137 tree_editor->change_file_prop = change_file_prop;
1138 tree_editor->close_file = close_file;
1139 tree_editor->close_edit = close_edit;
1140
1141 return svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1142 tree_editor, eb,
1143 editor, edit_baton,
1144 result_pool);
1145 }
1146
1147 svn_error_t *
svn_wc__crawl_file_external(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_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)1148 svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
1149 const char *local_abspath,
1150 const svn_ra_reporter3_t *reporter,
1151 void *report_baton,
1152 svn_boolean_t restore_files,
1153 svn_boolean_t use_commit_times,
1154 svn_cancel_func_t cancel_func,
1155 void *cancel_baton,
1156 svn_wc_notify_func2_t notify_func,
1157 void *notify_baton,
1158 apr_pool_t *scratch_pool)
1159 {
1160 svn_wc__db_t *db = wc_ctx->db;
1161 svn_error_t *err;
1162 svn_node_kind_t kind;
1163 svn_wc__db_lock_t *lock;
1164 svn_revnum_t revision;
1165 const char *repos_root_url;
1166 const char *repos_relpath;
1167 svn_boolean_t update_root;
1168
1169 err = svn_wc__db_base_get_info(NULL, &kind, &revision,
1170 &repos_relpath, &repos_root_url, NULL, NULL,
1171 NULL, NULL, NULL, NULL, NULL, &lock,
1172 NULL, NULL, &update_root,
1173 db, local_abspath,
1174 scratch_pool, scratch_pool);
1175
1176 if (err
1177 || kind == svn_node_dir
1178 || !update_root)
1179 {
1180 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1181 return svn_error_trace(err);
1182
1183 svn_error_clear(err);
1184
1185 /* We don't know about this node, so all we have to do is tell
1186 the reporter that we don't know this node.
1187
1188 But first we have to start the report by sending some basic
1189 information for the root. */
1190
1191 SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity,
1192 FALSE, NULL, scratch_pool));
1193 SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
1194
1195 /* Finish the report, which causes the update editor to be
1196 driven. */
1197 SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1198
1199 return SVN_NO_ERROR;
1200 }
1201 else
1202 {
1203 if (restore_files)
1204 {
1205 svn_node_kind_t disk_kind;
1206 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
1207
1208 if (disk_kind == svn_node_none)
1209 {
1210 err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times,
1211 scratch_pool);
1212
1213 if (err)
1214 {
1215 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1216 return svn_error_trace(err);
1217
1218 svn_error_clear(err);
1219 }
1220 }
1221 }
1222
1223 /* Report that we know the path */
1224 SVN_ERR(reporter->set_path(report_baton, "", revision,
1225 svn_depth_infinity, FALSE, NULL,
1226 scratch_pool));
1227
1228 /* For compatibility with the normal update editor report we report
1229 the target as switched.
1230
1231 ### We can probably report a parent url and unswitched later */
1232 SVN_ERR(reporter->link_path(report_baton, "",
1233 svn_path_url_add_component2(repos_root_url,
1234 repos_relpath,
1235 scratch_pool),
1236 revision,
1237 svn_depth_infinity,
1238 FALSE /* start_empty*/,
1239 lock ? lock->token : NULL,
1240 scratch_pool));
1241 }
1242
1243 return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
1244 }
1245
1246 svn_error_t *
svn_wc__read_external_info(svn_node_kind_t * external_kind,const char ** defining_abspath,const char ** defining_url,svn_revnum_t * defining_operational_revision,svn_revnum_t * defining_revision,svn_wc_context_t * wc_ctx,const char * wri_abspath,const char * local_abspath,svn_boolean_t ignore_enoent,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1247 svn_wc__read_external_info(svn_node_kind_t *external_kind,
1248 const char **defining_abspath,
1249 const char **defining_url,
1250 svn_revnum_t *defining_operational_revision,
1251 svn_revnum_t *defining_revision,
1252 svn_wc_context_t *wc_ctx,
1253 const char *wri_abspath,
1254 const char *local_abspath,
1255 svn_boolean_t ignore_enoent,
1256 apr_pool_t *result_pool,
1257 apr_pool_t *scratch_pool)
1258 {
1259 const char *repos_root_url;
1260 svn_wc__db_status_t status;
1261 svn_node_kind_t kind;
1262 svn_error_t *err;
1263
1264 err = svn_wc__db_external_read(&status, &kind, defining_abspath,
1265 defining_url ? &repos_root_url : NULL, NULL,
1266 defining_url, defining_operational_revision,
1267 defining_revision,
1268 wc_ctx->db, local_abspath, wri_abspath,
1269 result_pool, scratch_pool);
1270
1271 if (err)
1272 {
1273 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent)
1274 return svn_error_trace(err);
1275
1276 svn_error_clear(err);
1277
1278 if (external_kind)
1279 *external_kind = svn_node_none;
1280
1281 if (defining_abspath)
1282 *defining_abspath = NULL;
1283
1284 if (defining_url)
1285 *defining_url = NULL;
1286
1287 if (defining_operational_revision)
1288 *defining_operational_revision = SVN_INVALID_REVNUM;
1289
1290 if (defining_revision)
1291 *defining_revision = SVN_INVALID_REVNUM;
1292
1293 return SVN_NO_ERROR;
1294 }
1295
1296 if (external_kind)
1297 {
1298 if (status != svn_wc__db_status_normal)
1299 *external_kind = svn_node_unknown;
1300 else
1301 switch(kind)
1302 {
1303 case svn_node_file:
1304 case svn_node_symlink:
1305 *external_kind = svn_node_file;
1306 break;
1307 case svn_node_dir:
1308 *external_kind = svn_node_dir;
1309 break;
1310 default:
1311 *external_kind = svn_node_none;
1312 }
1313 }
1314
1315 if (defining_url && *defining_url)
1316 *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url,
1317 result_pool);
1318
1319 return SVN_NO_ERROR;
1320 }
1321
1322 /* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and
1323 * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and
1324 * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */
1325 static svn_error_t *
is_external_rolled_out(svn_boolean_t * is_rolled_out,svn_wc_context_t * wc_ctx,svn_wc__committable_external_info_t * xinfo,apr_pool_t * scratch_pool)1326 is_external_rolled_out(svn_boolean_t *is_rolled_out,
1327 svn_wc_context_t *wc_ctx,
1328 svn_wc__committable_external_info_t *xinfo,
1329 apr_pool_t *scratch_pool)
1330 {
1331 const char *repos_relpath;
1332 const char *repos_root_url;
1333 svn_error_t *err;
1334
1335 *is_rolled_out = FALSE;
1336
1337 err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
1338 &repos_root_url, NULL, NULL, NULL, NULL,
1339 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1340 wc_ctx->db, xinfo->local_abspath,
1341 scratch_pool, scratch_pool);
1342
1343 if (err)
1344 {
1345 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1346 {
1347 svn_error_clear(err);
1348 return SVN_NO_ERROR;
1349 }
1350 SVN_ERR(err);
1351 }
1352
1353 *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 &&
1354 strcmp(xinfo->repos_relpath, repos_relpath) == 0);
1355 return SVN_NO_ERROR;
1356 }
1357
1358 svn_error_t *
svn_wc__committable_externals_below(apr_array_header_t ** externals,svn_wc_context_t * wc_ctx,const char * local_abspath,svn_depth_t depth,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1359 svn_wc__committable_externals_below(apr_array_header_t **externals,
1360 svn_wc_context_t *wc_ctx,
1361 const char *local_abspath,
1362 svn_depth_t depth,
1363 apr_pool_t *result_pool,
1364 apr_pool_t *scratch_pool)
1365 {
1366 apr_array_header_t *orig_externals;
1367 int i;
1368 apr_pool_t *iterpool;
1369
1370 /* For svn_depth_files, this also fetches dirs. They are filtered later. */
1371 SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
1372 wc_ctx->db,
1373 local_abspath,
1374 depth != svn_depth_infinity,
1375 result_pool, scratch_pool));
1376
1377 if (orig_externals == NULL)
1378 return SVN_NO_ERROR;
1379
1380 iterpool = svn_pool_create(scratch_pool);
1381
1382 for (i = 0; i < orig_externals->nelts; i++)
1383 {
1384 svn_boolean_t is_rolled_out;
1385
1386 svn_wc__committable_external_info_t *xinfo =
1387 APR_ARRAY_IDX(orig_externals, i,
1388 svn_wc__committable_external_info_t *);
1389
1390 /* Discard dirs for svn_depth_files (s.a.). */
1391 if (depth == svn_depth_files
1392 && xinfo->kind == svn_node_dir)
1393 continue;
1394
1395 svn_pool_clear(iterpool);
1396
1397 /* Discard those externals that are not currently checked out. */
1398 SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo,
1399 iterpool));
1400 if (! is_rolled_out)
1401 continue;
1402
1403 if (*externals == NULL)
1404 *externals = apr_array_make(
1405 result_pool, 0,
1406 sizeof(svn_wc__committable_external_info_t *));
1407
1408 APR_ARRAY_PUSH(*externals,
1409 svn_wc__committable_external_info_t *) = xinfo;
1410
1411 if (depth != svn_depth_infinity)
1412 continue;
1413
1414 /* Are there any nested externals? */
1415 SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
1416 xinfo->local_abspath,
1417 svn_depth_infinity,
1418 result_pool, iterpool));
1419 }
1420
1421 return SVN_NO_ERROR;
1422 }
1423
1424 svn_error_t *
svn_wc__externals_defined_below(apr_hash_t ** externals,svn_wc_context_t * wc_ctx,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1425 svn_wc__externals_defined_below(apr_hash_t **externals,
1426 svn_wc_context_t *wc_ctx,
1427 const char *local_abspath,
1428 apr_pool_t *result_pool,
1429 apr_pool_t *scratch_pool)
1430 {
1431 return svn_error_trace(
1432 svn_wc__db_externals_defined_below(externals,
1433 wc_ctx->db, local_abspath,
1434 result_pool, scratch_pool));
1435 }
1436
1437 svn_error_t *
svn_wc__external_register(svn_wc_context_t * wc_ctx,const char * defining_abspath,const char * local_abspath,svn_node_kind_t kind,const char * repos_root_url,const char * repos_uuid,const char * repos_relpath,svn_revnum_t operational_revision,svn_revnum_t revision,apr_pool_t * scratch_pool)1438 svn_wc__external_register(svn_wc_context_t *wc_ctx,
1439 const char *defining_abspath,
1440 const char *local_abspath,
1441 svn_node_kind_t kind,
1442 const char *repos_root_url,
1443 const char *repos_uuid,
1444 const char *repos_relpath,
1445 svn_revnum_t operational_revision,
1446 svn_revnum_t revision,
1447 apr_pool_t *scratch_pool)
1448 {
1449 SVN_ERR_ASSERT(kind == svn_node_dir);
1450 return svn_error_trace(
1451 svn_wc__db_external_add_dir(wc_ctx->db, local_abspath,
1452 defining_abspath,
1453 repos_root_url,
1454 repos_uuid,
1455 defining_abspath,
1456 repos_relpath,
1457 operational_revision,
1458 revision,
1459 NULL,
1460 scratch_pool));
1461 }
1462
1463 svn_error_t *
svn_wc__external_remove(svn_wc_context_t * wc_ctx,const char * wri_abspath,const char * local_abspath,svn_boolean_t declaration_only,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)1464 svn_wc__external_remove(svn_wc_context_t *wc_ctx,
1465 const char *wri_abspath,
1466 const char *local_abspath,
1467 svn_boolean_t declaration_only,
1468 svn_cancel_func_t cancel_func,
1469 void *cancel_baton,
1470 apr_pool_t *scratch_pool)
1471 {
1472 svn_wc__db_status_t status;
1473 svn_node_kind_t kind;
1474
1475 SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL,
1476 NULL, NULL,
1477 wc_ctx->db, local_abspath, wri_abspath,
1478 scratch_pool, scratch_pool));
1479
1480 SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath,
1481 NULL, scratch_pool));
1482
1483 if (declaration_only)
1484 return SVN_NO_ERROR;
1485
1486 if (kind == svn_node_dir)
1487 SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath,
1488 TRUE, TRUE,
1489 cancel_func, cancel_baton,
1490 scratch_pool));
1491 else
1492 {
1493 SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
1494 FALSE, TRUE, FALSE,
1495 0,
1496 NULL, NULL, scratch_pool));
1497 SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
1498 cancel_func, cancel_baton,
1499 scratch_pool));
1500 }
1501
1502 return SVN_NO_ERROR;
1503 }
1504
1505 svn_error_t *
svn_wc__externals_gather_definitions(apr_hash_t ** externals,apr_hash_t ** depths,svn_wc_context_t * wc_ctx,const char * local_abspath,svn_depth_t depth,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1506 svn_wc__externals_gather_definitions(apr_hash_t **externals,
1507 apr_hash_t **depths,
1508 svn_wc_context_t *wc_ctx,
1509 const char *local_abspath,
1510 svn_depth_t depth,
1511 apr_pool_t *result_pool,
1512 apr_pool_t *scratch_pool)
1513 {
1514 if (depth == svn_depth_infinity
1515 || depth == svn_depth_unknown)
1516 {
1517 return svn_error_trace(
1518 svn_wc__db_externals_gather_definitions(externals, depths,
1519 wc_ctx->db, local_abspath,
1520 result_pool, scratch_pool));
1521 }
1522 else
1523 {
1524 const svn_string_t *value;
1525 svn_error_t *err;
1526 *externals = apr_hash_make(result_pool);
1527
1528 local_abspath = apr_pstrdup(result_pool, local_abspath);
1529
1530 err = svn_wc_prop_get2(&value, wc_ctx, local_abspath,
1531 SVN_PROP_EXTERNALS, result_pool, scratch_pool);
1532
1533 if (err)
1534 {
1535 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1536 return svn_error_trace(err);
1537
1538 svn_error_clear(err);
1539 value = NULL;
1540 }
1541
1542 if (value)
1543 svn_hash_sets(*externals, local_abspath, value->data);
1544
1545 if (value && depths)
1546 {
1547 svn_depth_t node_depth;
1548 *depths = apr_hash_make(result_pool);
1549
1550 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
1551 NULL, NULL, NULL, &node_depth, NULL,
1552 NULL, NULL, NULL, NULL, NULL, NULL,
1553 NULL, NULL, NULL, NULL, NULL, NULL,
1554 NULL, NULL, NULL, NULL,
1555 wc_ctx->db, local_abspath,
1556 scratch_pool, scratch_pool));
1557
1558 svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth));
1559 }
1560
1561 return SVN_NO_ERROR;
1562 }
1563 }
1564
1565 svn_error_t *
svn_wc__close_db(const char * external_abspath,svn_wc_context_t * wc_ctx,apr_pool_t * scratch_pool)1566 svn_wc__close_db(const char *external_abspath,
1567 svn_wc_context_t *wc_ctx,
1568 apr_pool_t *scratch_pool)
1569 {
1570 SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath,
1571 scratch_pool));
1572 return SVN_NO_ERROR;
1573 }
1574
1575 /* Return the scheme of @a uri in @a scheme allocated from @a pool.
1576 If @a uri does not appear to be a valid URI, then @a scheme will
1577 not be updated. */
1578 static svn_error_t *
uri_scheme(const char ** scheme,const char * uri,apr_pool_t * pool)1579 uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
1580 {
1581 apr_size_t i;
1582
1583 for (i = 0; uri[i] && uri[i] != ':'; ++i)
1584 if (uri[i] == '/')
1585 goto error;
1586
1587 if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
1588 {
1589 *scheme = apr_pstrmemdup(pool, uri, i);
1590 return SVN_NO_ERROR;
1591 }
1592
1593 error:
1594 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1595 _("URL '%s' does not begin with a scheme"),
1596 uri);
1597 }
1598
1599 svn_error_t *
svn_wc__resolve_relative_external_url(const char ** resolved_url,const svn_wc_external_item2_t * item,const char * repos_root_url,const char * parent_dir_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1600 svn_wc__resolve_relative_external_url(const char **resolved_url,
1601 const svn_wc_external_item2_t *item,
1602 const char *repos_root_url,
1603 const char *parent_dir_url,
1604 apr_pool_t *result_pool,
1605 apr_pool_t *scratch_pool)
1606 {
1607 const char *url = item->url;
1608 apr_uri_t parent_dir_uri;
1609 apr_status_t status;
1610
1611 *resolved_url = item->url;
1612
1613 /* If the URL is already absolute, there is nothing to do. */
1614 if (svn_path_is_url(url))
1615 {
1616 /* "http://server/path" */
1617 *resolved_url = svn_uri_canonicalize(url, result_pool);
1618 return SVN_NO_ERROR;
1619 }
1620
1621 if (url[0] == '/')
1622 {
1623 /* "/path", "//path", and "///path" */
1624 int num_leading_slashes = 1;
1625 if (url[1] == '/')
1626 {
1627 num_leading_slashes++;
1628 if (url[2] == '/')
1629 num_leading_slashes++;
1630 }
1631
1632 /* "//schema-relative" and in some cases "///schema-relative".
1633 This last format is supported on file:// schema relative. */
1634 url = apr_pstrcat(scratch_pool,
1635 apr_pstrndup(scratch_pool, url, num_leading_slashes),
1636 svn_relpath_canonicalize(url + num_leading_slashes,
1637 scratch_pool),
1638 SVN_VA_NULL);
1639 }
1640 else
1641 {
1642 /* "^/path" and "../path" */
1643 url = svn_relpath_canonicalize(url, scratch_pool);
1644 }
1645
1646 /* Parse the parent directory URL into its parts. */
1647 status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
1648 if (status)
1649 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1650 _("Illegal parent directory URL '%s'"),
1651 parent_dir_url);
1652
1653 /* If the parent directory URL is at the server root, then the URL
1654 may have no / after the hostname so apr_uri_parse() will leave
1655 the URL's path as NULL. */
1656 if (! parent_dir_uri.path)
1657 parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1658 parent_dir_uri.query = NULL;
1659 parent_dir_uri.fragment = NULL;
1660
1661 /* Handle URLs relative to the current directory or to the
1662 repository root. The backpaths may only remove path elements,
1663 not the hostname. This allows an external to refer to another
1664 repository in the same server relative to the location of this
1665 repository, say using SVNParentPath. */
1666 if ((0 == strncmp("../", url, 3)) ||
1667 (0 == strncmp("^/", url, 2)))
1668 {
1669 apr_array_header_t *base_components;
1670 apr_array_header_t *relative_components;
1671 int i;
1672
1673 /* Decompose either the parent directory's URL path or the
1674 repository root's URL path into components. */
1675 if (0 == strncmp("../", url, 3))
1676 {
1677 base_components = svn_path_decompose(parent_dir_uri.path,
1678 scratch_pool);
1679 relative_components = svn_path_decompose(url, scratch_pool);
1680 }
1681 else
1682 {
1683 apr_uri_t repos_root_uri;
1684
1685 status = apr_uri_parse(scratch_pool, repos_root_url,
1686 &repos_root_uri);
1687 if (status)
1688 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1689 _("Illegal repository root URL '%s'"),
1690 repos_root_url);
1691
1692 /* If the repository root URL is at the server root, then
1693 the URL may have no / after the hostname so
1694 apr_uri_parse() will leave the URL's path as NULL. */
1695 if (! repos_root_uri.path)
1696 repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1697
1698 base_components = svn_path_decompose(repos_root_uri.path,
1699 scratch_pool);
1700 relative_components = svn_path_decompose(url + 2, scratch_pool);
1701 }
1702
1703 for (i = 0; i < relative_components->nelts; ++i)
1704 {
1705 const char *component = APR_ARRAY_IDX(relative_components,
1706 i,
1707 const char *);
1708 if (0 == strcmp("..", component))
1709 {
1710 /* Constructing the final absolute URL together with
1711 apr_uri_unparse() requires that the path be absolute,
1712 so only pop a component if the component being popped
1713 is not the component for the root directory. */
1714 if (base_components->nelts > 1)
1715 apr_array_pop(base_components);
1716 }
1717 else
1718 APR_ARRAY_PUSH(base_components, const char *) = component;
1719 }
1720
1721 parent_dir_uri.path = (char *)svn_path_compose(base_components,
1722 scratch_pool);
1723 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1724 &parent_dir_uri, 0),
1725 result_pool);
1726 return SVN_NO_ERROR;
1727 }
1728
1729 /* The remaining URLs are relative to either the scheme or server root
1730 and can only refer to locations inside that scope, so backpaths are
1731 not allowed. */
1732 if (svn_path_is_backpath_present(url))
1733 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1734 _("The external relative URL '%s' cannot have "
1735 "backpaths, i.e. '..'"),
1736 item->url);
1737
1738 /* Relative to the scheme: Build a new URL from the parts we know. */
1739 if (0 == strncmp("//", url, 2))
1740 {
1741 const char *scheme;
1742
1743 SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
1744 *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
1745 ":", url, SVN_VA_NULL),
1746 result_pool);
1747 return SVN_NO_ERROR;
1748 }
1749
1750 /* Relative to the server root: Just replace the path portion of the
1751 parent's URL. */
1752 if (url[0] == '/')
1753 {
1754 parent_dir_uri.path = (char *)url;
1755 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1756 &parent_dir_uri, 0),
1757 result_pool);
1758 return SVN_NO_ERROR;
1759 }
1760
1761 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1762 _("Unrecognized format for the relative external "
1763 "URL '%s'"),
1764 item->url);
1765 }
1766