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_cancel_func_t cancel_func;
479 void *cancel_baton;
480 svn_wc_notify_func2_t notify_func;
481 void *notify_baton;
482
483 svn_revnum_t *target_revision;
484
485 /* What was there before the update */
486 svn_revnum_t original_revision;
487 const svn_checksum_t *original_checksum;
488
489 /* What we are installing now */
490 svn_wc__db_install_data_t *install_data;
491 svn_checksum_t *new_sha1_checksum;
492 svn_checksum_t *new_md5_checksum;
493
494 /* List of incoming propchanges */
495 apr_array_header_t *propchanges;
496
497 /* Array of svn_prop_inherited_item_t * structures representing the
498 properties inherited by the base node at LOCAL_ABSPATH. */
499 apr_array_header_t *iprops;
500
501 /* The last change information */
502 svn_revnum_t changed_rev;
503 apr_time_t changed_date;
504 const char *changed_author;
505
506 svn_boolean_t had_props;
507
508 svn_boolean_t file_closed;
509 };
510
511 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
512 static svn_error_t *
set_target_revision(void * edit_baton,svn_revnum_t target_revision,apr_pool_t * pool)513 set_target_revision(void *edit_baton,
514 svn_revnum_t target_revision,
515 apr_pool_t *pool)
516 {
517 struct edit_baton *eb = edit_baton;
518
519 *eb->target_revision = target_revision;
520
521 return SVN_NO_ERROR;
522 }
523
524 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
525 static svn_error_t *
open_root(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * dir_pool,void ** root_baton)526 open_root(void *edit_baton,
527 svn_revnum_t base_revision,
528 apr_pool_t *dir_pool,
529 void **root_baton)
530 {
531 *root_baton = edit_baton;
532 return SVN_NO_ERROR;
533 }
534
535 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
536 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)537 add_file(const char *path,
538 void *parent_baton,
539 const char *copyfrom_path,
540 svn_revnum_t copyfrom_revision,
541 apr_pool_t *file_pool,
542 void **file_baton)
543 {
544 struct edit_baton *eb = parent_baton;
545 if (strcmp(path, eb->name))
546 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
547 _("This editor can only update '%s'"),
548 svn_dirent_local_style(eb->local_abspath,
549 file_pool));
550
551 *file_baton = eb;
552 eb->original_revision = SVN_INVALID_REVNUM;
553 eb->added = TRUE;
554
555 return SVN_NO_ERROR;
556 }
557
558 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
559 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)560 open_file(const char *path,
561 void *parent_baton,
562 svn_revnum_t base_revision,
563 apr_pool_t *file_pool,
564 void **file_baton)
565 {
566 struct edit_baton *eb = parent_baton;
567 svn_node_kind_t kind;
568 if (strcmp(path, eb->name))
569 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
570 _("This editor can only update '%s'"),
571 svn_dirent_local_style(eb->local_abspath,
572 file_pool));
573
574 *file_baton = eb;
575 SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision,
576 &eb->old_repos_relpath, NULL, NULL,
577 &eb->changed_rev,
578 &eb->changed_date, &eb->changed_author,
579 NULL, &eb->original_checksum, NULL, NULL,
580 &eb->had_props, NULL, NULL,
581 eb->db, eb->local_abspath,
582 eb->pool, file_pool));
583
584 if (kind != svn_node_file)
585 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
586 _("Node '%s' is no existing file external"),
587 svn_dirent_local_style(eb->local_abspath,
588 file_pool));
589 return SVN_NO_ERROR;
590 }
591
592 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
593 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)594 apply_textdelta(void *file_baton,
595 const char *base_checksum_digest,
596 apr_pool_t *pool,
597 svn_txdelta_window_handler_t *handler,
598 void **handler_baton)
599 {
600 struct edit_baton *eb = file_baton;
601 svn_stream_t *src_stream;
602 svn_stream_t *dest_stream;
603
604 if (eb->original_checksum)
605 {
606 if (base_checksum_digest)
607 {
608 svn_checksum_t *expected_checksum;
609 const svn_checksum_t *original_md5;
610
611 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
612 base_checksum_digest, pool));
613
614 if (eb->original_checksum->kind != svn_checksum_md5)
615 SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5,
616 eb->db, eb->wri_abspath,
617 eb->original_checksum,
618 pool, pool));
619 else
620 original_md5 = eb->original_checksum;
621
622 if (!svn_checksum_match(expected_checksum, original_md5))
623 return svn_error_trace(svn_checksum_mismatch_err(
624 expected_checksum,
625 original_md5,
626 pool,
627 _("Base checksum mismatch for '%s'"),
628 svn_dirent_local_style(eb->local_abspath,
629 pool)));
630 }
631
632 SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db,
633 eb->wri_abspath, eb->original_checksum,
634 pool, pool));
635 }
636 else
637 src_stream = svn_stream_empty(pool);
638
639 SVN_ERR(svn_wc__db_pristine_prepare_install(&dest_stream,
640 &eb->install_data,
641 &eb->new_sha1_checksum,
642 &eb->new_md5_checksum,
643 eb->db, eb->wri_abspath,
644 eb->pool, pool));
645
646 svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool,
647 handler, handler_baton);
648
649 return SVN_NO_ERROR;
650 }
651
652 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
653 static svn_error_t *
change_file_prop(void * file_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)654 change_file_prop(void *file_baton,
655 const char *name,
656 const svn_string_t *value,
657 apr_pool_t *pool)
658 {
659 struct edit_baton *eb = file_baton;
660 svn_prop_t *propchange;
661
662 propchange = apr_array_push(eb->propchanges);
663 propchange->name = apr_pstrdup(eb->pool, name);
664 propchange->value = svn_string_dup(value, eb->pool);
665
666 return SVN_NO_ERROR;
667 }
668
669 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
670 static svn_error_t *
close_file(void * file_baton,const char * expected_md5_digest,apr_pool_t * pool)671 close_file(void *file_baton,
672 const char *expected_md5_digest,
673 apr_pool_t *pool)
674 {
675 struct edit_baton *eb = file_baton;
676 svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
677 svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown;
678 svn_boolean_t obstructed = FALSE;
679
680 eb->file_closed = TRUE; /* We bump the revision here */
681
682 /* Check the checksum, if provided */
683 if (expected_md5_digest)
684 {
685 svn_checksum_t *expected_md5_checksum;
686 const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum;
687
688 SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
689 expected_md5_digest, pool));
690
691 if (actual_md5_checksum == NULL)
692 {
693 actual_md5_checksum = eb->original_checksum;
694
695 if (actual_md5_checksum != NULL
696 && actual_md5_checksum->kind != svn_checksum_md5)
697 {
698 SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum,
699 eb->db, eb->wri_abspath,
700 actual_md5_checksum,
701 pool, pool));
702 }
703 }
704
705 if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum))
706 return svn_checksum_mismatch_err(
707 expected_md5_checksum,
708 actual_md5_checksum, pool,
709 _("Checksum mismatch for '%s'"),
710 svn_dirent_local_style(eb->local_abspath, pool));
711 }
712
713 /* First move the file in the pristine store; this hands over the cleanup
714 behavior to the pristine store. */
715 if (eb->new_sha1_checksum)
716 {
717 SVN_ERR(svn_wc__db_pristine_install(eb->install_data,
718 eb->new_sha1_checksum,
719 eb->new_md5_checksum, pool));
720
721 eb->install_data = NULL;
722 }
723
724 /* Merge the changes */
725 {
726 svn_skel_t *all_work_items = NULL;
727 svn_skel_t *conflict_skel = NULL;
728 svn_skel_t *work_item;
729 apr_hash_t *base_props = NULL;
730 apr_hash_t *actual_props = NULL;
731 apr_hash_t *new_pristine_props = NULL;
732 apr_hash_t *new_actual_props = NULL;
733 apr_hash_t *new_dav_props = NULL;
734 const svn_checksum_t *new_checksum = NULL;
735 const svn_checksum_t *original_checksum = NULL;
736
737 svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision);
738
739 if (! added)
740 {
741 new_checksum = eb->original_checksum;
742
743 if (eb->had_props)
744 SVN_ERR(svn_wc__db_base_get_props(
745 &base_props, eb->db, eb->local_abspath, pool, pool));
746
747 SVN_ERR(svn_wc__db_read_props(
748 &actual_props, eb->db, eb->local_abspath, pool, pool));
749 }
750
751 if (!base_props)
752 base_props = apr_hash_make(pool);
753
754 if (!actual_props)
755 actual_props = apr_hash_make(pool);
756
757 if (eb->new_sha1_checksum)
758 new_checksum = eb->new_sha1_checksum;
759
760 /* Merge the properties */
761 {
762 apr_array_header_t *entry_prop_changes;
763 apr_array_header_t *dav_prop_changes;
764 apr_array_header_t *regular_prop_changes;
765 int i;
766
767 SVN_ERR(svn_categorize_props(eb->propchanges, &entry_prop_changes,
768 &dav_prop_changes, ®ular_prop_changes,
769 pool));
770
771 /* Read the entry-prop changes to update the last-changed info. */
772 for (i = 0; i < entry_prop_changes->nelts; i++)
773 {
774 const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i,
775 svn_prop_t);
776
777 if (! prop->value)
778 continue; /* authz or something */
779
780 if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR))
781 eb->changed_author = apr_pstrdup(pool, prop->value->data);
782 else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV))
783 {
784 apr_int64_t rev;
785 SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data));
786 eb->changed_rev = (svn_revnum_t)rev;
787 }
788 else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE))
789 SVN_ERR(svn_time_from_cstring(&eb->changed_date, prop->value->data,
790 pool));
791 }
792
793 /* Store the DAV-prop (aka WC-prop) changes. (This treats a list
794 * of changes as a list of new props, but we only use this when
795 * adding a new file and it's equivalent in that case.) */
796 if (dav_prop_changes->nelts > 0)
797 new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool);
798
799 /* Merge the regular prop changes. */
800 if (regular_prop_changes->nelts > 0)
801 {
802 new_pristine_props = svn_prop__patch(base_props, regular_prop_changes,
803 pool);
804 SVN_ERR(svn_wc__merge_props(&conflict_skel,
805 &prop_state,
806 &new_actual_props,
807 eb->db, eb->local_abspath,
808 NULL /* server_baseprops*/,
809 base_props,
810 actual_props,
811 regular_prop_changes,
812 pool, pool));
813 }
814 else
815 {
816 new_pristine_props = base_props;
817 new_actual_props = actual_props;
818 }
819 }
820
821 /* Merge the text */
822 if (eb->new_sha1_checksum)
823 {
824 svn_node_kind_t disk_kind;
825 svn_boolean_t install_pristine = FALSE;
826
827 SVN_ERR(svn_io_check_path(eb->local_abspath, &disk_kind, pool));
828
829 if (disk_kind == svn_node_none)
830 {
831 /* Just install the file */
832 install_pristine = TRUE;
833 content_state = svn_wc_notify_state_changed;
834 }
835 else if (disk_kind != svn_node_file
836 || (eb->added && disk_kind == svn_node_file))
837 {
838 /* The node is obstructed; we just change the DB */
839 obstructed = TRUE;
840 content_state = svn_wc_notify_state_unchanged;
841 }
842 else
843 {
844 svn_boolean_t is_mod;
845 SVN_ERR(svn_wc__internal_file_modified_p(&is_mod,
846 eb->db, eb->local_abspath,
847 FALSE, pool));
848
849 if (!is_mod)
850 {
851 install_pristine = TRUE;
852 content_state = svn_wc_notify_state_changed;
853 }
854 else
855 {
856 svn_boolean_t found_text_conflict;
857
858 /* Ok, we have to do some work to merge a local change */
859 SVN_ERR(svn_wc__perform_file_merge(&work_item,
860 &conflict_skel,
861 &found_text_conflict,
862 eb->db,
863 eb->local_abspath,
864 eb->wri_abspath,
865 new_checksum,
866 original_checksum,
867 actual_props,
868 eb->ext_patterns,
869 eb->original_revision,
870 *eb->target_revision,
871 eb->propchanges,
872 eb->diff3cmd,
873 eb->cancel_func,
874 eb->cancel_baton,
875 pool, pool));
876
877 all_work_items = svn_wc__wq_merge(all_work_items, work_item,
878 pool);
879
880 if (found_text_conflict)
881 content_state = svn_wc_notify_state_conflicted;
882 else
883 content_state = svn_wc_notify_state_merged;
884 }
885 }
886 if (install_pristine)
887 {
888 SVN_ERR(svn_wc__wq_build_file_install(&work_item, eb->db,
889 eb->local_abspath,
890 NULL,
891 eb->use_commit_times, TRUE,
892 pool, pool));
893
894 all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
895 }
896 }
897 else
898 {
899 content_state = svn_wc_notify_state_unchanged;
900 /* ### Retranslate on magic property changes, etc. */
901 }
902
903 /* Generate a conflict description, if needed */
904 if (conflict_skel)
905 {
906 SVN_ERR(svn_wc__conflict_skel_set_op_switch(
907 conflict_skel,
908 svn_wc_conflict_version_create2(
909 eb->repos_root_url,
910 eb->repos_uuid,
911 eb->old_repos_relpath,
912 eb->original_revision,
913 svn_node_file,
914 pool),
915 svn_wc_conflict_version_create2(
916 eb->repos_root_url,
917 eb->repos_uuid,
918 eb->new_repos_relpath,
919 *eb->target_revision,
920 svn_node_file,
921 pool),
922 pool, pool));
923 SVN_ERR(svn_wc__conflict_create_markers(&work_item,
924 eb->db, eb->local_abspath,
925 conflict_skel,
926 pool, pool));
927 all_work_items = svn_wc__wq_merge(all_work_items, work_item,
928 pool);
929 }
930
931 /* Install the file in the DB */
932 SVN_ERR(svn_wc__db_external_add_file(
933 eb->db,
934 eb->local_abspath,
935 eb->wri_abspath,
936 eb->new_repos_relpath,
937 eb->repos_root_url,
938 eb->repos_uuid,
939 *eb->target_revision,
940 new_pristine_props,
941 eb->iprops,
942 eb->changed_rev,
943 eb->changed_date,
944 eb->changed_author,
945 new_checksum,
946 new_dav_props,
947 eb->record_ancestor_abspath,
948 eb->recorded_repos_relpath,
949 eb->recorded_peg_revision,
950 eb->recorded_revision,
951 TRUE, new_actual_props,
952 FALSE /* keep_recorded_info */,
953 conflict_skel,
954 all_work_items,
955 pool));
956
957 /* close_edit may also update iprops for switched files, catching
958 those for which close_file is never called (e.g. an update of a
959 file external with no changes). So as a minor optimization we
960 clear the iprops so as not to set them again in close_edit. */
961 eb->iprops = NULL;
962
963 /* Run the work queue to complete the installation */
964 SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath,
965 eb->cancel_func, eb->cancel_baton, pool));
966 }
967
968 /* Notify */
969 if (eb->notify_func)
970 {
971 svn_wc_notify_action_t action;
972 svn_wc_notify_t *notify;
973
974 if (!eb->added)
975 action = obstructed ? svn_wc_notify_update_shadowed_update
976 : svn_wc_notify_update_update;
977 else
978 action = obstructed ? svn_wc_notify_update_shadowed_add
979 : svn_wc_notify_update_add;
980
981 notify = svn_wc_create_notify(eb->local_abspath, action, pool);
982 notify->kind = svn_node_file;
983
984 notify->revision = *eb->target_revision;
985 notify->prop_state = prop_state;
986 notify->content_state = content_state;
987
988 notify->old_revision = eb->original_revision;
989
990 eb->notify_func(eb->notify_baton, notify, pool);
991 }
992
993 return SVN_NO_ERROR;
994 }
995
996 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
997 static svn_error_t *
close_edit(void * edit_baton,apr_pool_t * pool)998 close_edit(void *edit_baton,
999 apr_pool_t *pool)
1000 {
1001 struct edit_baton *eb = edit_baton;
1002
1003 if (!eb->file_closed)
1004 {
1005 apr_hash_t *wcroot_iprops = NULL;
1006 /* The file wasn't updated, but its url or revision might have...
1007 e.g. switch between branches for relative externals.
1008
1009 Just bump the information as that is just as expensive as
1010 investigating when we should and shouldn't update it...
1011 and avoid hard to debug edge cases */
1012
1013 if (eb->iprops)
1014 {
1015 wcroot_iprops = apr_hash_make(pool);
1016 svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops);
1017 }
1018
1019 SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
1020 eb->local_abspath,
1021 svn_depth_infinity,
1022 eb->new_repos_relpath,
1023 eb->repos_root_url,
1024 eb->repos_uuid,
1025 *eb->target_revision,
1026 apr_hash_make(pool)
1027 /* exclude_relpaths */,
1028 wcroot_iprops,
1029 TRUE /* empty update */,
1030 eb->notify_func,
1031 eb->notify_baton,
1032 pool));
1033 }
1034
1035 return SVN_NO_ERROR;
1036 }
1037
1038 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_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)1039 svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
1040 void **edit_baton,
1041 svn_revnum_t *target_revision,
1042 svn_wc_context_t *wc_ctx,
1043 const char *local_abspath,
1044 const char *wri_abspath,
1045 const char *url,
1046 const char *repos_root_url,
1047 const char *repos_uuid,
1048 apr_array_header_t *iprops,
1049 svn_boolean_t use_commit_times,
1050 const char *diff3_cmd,
1051 const apr_array_header_t *preserved_exts,
1052 const char *record_ancestor_abspath,
1053 const char *recorded_url,
1054 const svn_opt_revision_t *recorded_peg_rev,
1055 const svn_opt_revision_t *recorded_rev,
1056 svn_cancel_func_t cancel_func,
1057 void *cancel_baton,
1058 svn_wc_notify_func2_t notify_func,
1059 void *notify_baton,
1060 apr_pool_t *result_pool,
1061 apr_pool_t *scratch_pool)
1062 {
1063 svn_wc__db_t *db = wc_ctx->db;
1064 apr_pool_t *edit_pool = result_pool;
1065 struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb));
1066 svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
1067
1068 eb->pool = edit_pool;
1069 eb->db = db;
1070 eb->local_abspath = apr_pstrdup(edit_pool, local_abspath);
1071 if (wri_abspath)
1072 eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath);
1073 else
1074 eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool);
1075 eb->name = svn_dirent_basename(eb->local_abspath, NULL);
1076 eb->target_revision = target_revision;
1077
1078 eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
1079 eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
1080 eb->new_repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url, url, edit_pool);
1081 eb->old_repos_relpath = eb->new_repos_relpath;
1082
1083 eb->original_revision = SVN_INVALID_REVNUM;
1084
1085 eb->iprops = iprops;
1086
1087 eb->use_commit_times = use_commit_times;
1088 eb->ext_patterns = preserved_exts;
1089 eb->diff3cmd = diff3_cmd;
1090
1091 eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath);
1092 eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url,
1093 edit_pool);
1094
1095 eb->changed_rev = SVN_INVALID_REVNUM;
1096
1097 if (recorded_peg_rev->kind == svn_opt_revision_number)
1098 eb->recorded_peg_revision = recorded_peg_rev->value.number;
1099 else
1100 eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1101
1102 if (recorded_rev->kind == svn_opt_revision_number)
1103 eb->recorded_revision = recorded_rev->value.number;
1104 else
1105 eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1106
1107 eb->cancel_func = cancel_func;
1108 eb->cancel_baton = cancel_baton;
1109 eb->notify_func = notify_func;
1110 eb->notify_baton = notify_baton;
1111
1112 eb->propchanges = apr_array_make(edit_pool, 1, sizeof(svn_prop_t));
1113
1114 tree_editor->open_root = open_root;
1115 tree_editor->set_target_revision = set_target_revision;
1116 tree_editor->add_file = add_file;
1117 tree_editor->open_file = open_file;
1118 tree_editor->apply_textdelta = apply_textdelta;
1119 tree_editor->change_file_prop = change_file_prop;
1120 tree_editor->close_file = close_file;
1121 tree_editor->close_edit = close_edit;
1122
1123 return svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1124 tree_editor, eb,
1125 editor, edit_baton,
1126 result_pool);
1127 }
1128
1129 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)1130 svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
1131 const char *local_abspath,
1132 const svn_ra_reporter3_t *reporter,
1133 void *report_baton,
1134 svn_boolean_t restore_files,
1135 svn_boolean_t use_commit_times,
1136 svn_cancel_func_t cancel_func,
1137 void *cancel_baton,
1138 svn_wc_notify_func2_t notify_func,
1139 void *notify_baton,
1140 apr_pool_t *scratch_pool)
1141 {
1142 svn_wc__db_t *db = wc_ctx->db;
1143 svn_error_t *err;
1144 svn_node_kind_t kind;
1145 svn_wc__db_lock_t *lock;
1146 svn_revnum_t revision;
1147 const char *repos_root_url;
1148 const char *repos_relpath;
1149 svn_boolean_t update_root;
1150
1151 err = svn_wc__db_base_get_info(NULL, &kind, &revision,
1152 &repos_relpath, &repos_root_url, NULL, NULL,
1153 NULL, NULL, NULL, NULL, NULL, &lock,
1154 NULL, NULL, &update_root,
1155 db, local_abspath,
1156 scratch_pool, scratch_pool);
1157
1158 if (err
1159 || kind == svn_node_dir
1160 || !update_root)
1161 {
1162 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1163 return svn_error_trace(err);
1164
1165 svn_error_clear(err);
1166
1167 /* We don't know about this node, so all we have to do is tell
1168 the reporter that we don't know this node.
1169
1170 But first we have to start the report by sending some basic
1171 information for the root. */
1172
1173 SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity,
1174 FALSE, NULL, scratch_pool));
1175 SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
1176
1177 /* Finish the report, which causes the update editor to be
1178 driven. */
1179 SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1180
1181 return SVN_NO_ERROR;
1182 }
1183 else
1184 {
1185 if (restore_files)
1186 {
1187 svn_node_kind_t disk_kind;
1188 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
1189
1190 if (disk_kind == svn_node_none)
1191 {
1192 err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times,
1193 scratch_pool);
1194
1195 if (err)
1196 {
1197 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1198 return svn_error_trace(err);
1199
1200 svn_error_clear(err);
1201 }
1202 }
1203 }
1204
1205 /* Report that we know the path */
1206 SVN_ERR(reporter->set_path(report_baton, "", revision,
1207 svn_depth_infinity, FALSE, NULL,
1208 scratch_pool));
1209
1210 /* For compatibility with the normal update editor report we report
1211 the target as switched.
1212
1213 ### We can probably report a parent url and unswitched later */
1214 SVN_ERR(reporter->link_path(report_baton, "",
1215 svn_path_url_add_component2(repos_root_url,
1216 repos_relpath,
1217 scratch_pool),
1218 revision,
1219 svn_depth_infinity,
1220 FALSE /* start_empty*/,
1221 lock ? lock->token : NULL,
1222 scratch_pool));
1223 }
1224
1225 return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
1226 }
1227
1228 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)1229 svn_wc__read_external_info(svn_node_kind_t *external_kind,
1230 const char **defining_abspath,
1231 const char **defining_url,
1232 svn_revnum_t *defining_operational_revision,
1233 svn_revnum_t *defining_revision,
1234 svn_wc_context_t *wc_ctx,
1235 const char *wri_abspath,
1236 const char *local_abspath,
1237 svn_boolean_t ignore_enoent,
1238 apr_pool_t *result_pool,
1239 apr_pool_t *scratch_pool)
1240 {
1241 const char *repos_root_url;
1242 svn_wc__db_status_t status;
1243 svn_node_kind_t kind;
1244 svn_error_t *err;
1245
1246 err = svn_wc__db_external_read(&status, &kind, defining_abspath,
1247 defining_url ? &repos_root_url : NULL, NULL,
1248 defining_url, defining_operational_revision,
1249 defining_revision,
1250 wc_ctx->db, local_abspath, wri_abspath,
1251 result_pool, scratch_pool);
1252
1253 if (err)
1254 {
1255 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent)
1256 return svn_error_trace(err);
1257
1258 svn_error_clear(err);
1259
1260 if (external_kind)
1261 *external_kind = svn_node_none;
1262
1263 if (defining_abspath)
1264 *defining_abspath = NULL;
1265
1266 if (defining_url)
1267 *defining_url = NULL;
1268
1269 if (defining_operational_revision)
1270 *defining_operational_revision = SVN_INVALID_REVNUM;
1271
1272 if (defining_revision)
1273 *defining_revision = SVN_INVALID_REVNUM;
1274
1275 return SVN_NO_ERROR;
1276 }
1277
1278 if (external_kind)
1279 {
1280 if (status != svn_wc__db_status_normal)
1281 *external_kind = svn_node_unknown;
1282 else
1283 switch(kind)
1284 {
1285 case svn_node_file:
1286 case svn_node_symlink:
1287 *external_kind = svn_node_file;
1288 break;
1289 case svn_node_dir:
1290 *external_kind = svn_node_dir;
1291 break;
1292 default:
1293 *external_kind = svn_node_none;
1294 }
1295 }
1296
1297 if (defining_url && *defining_url)
1298 *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url,
1299 result_pool);
1300
1301 return SVN_NO_ERROR;
1302 }
1303
1304 /* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and
1305 * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and
1306 * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */
1307 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)1308 is_external_rolled_out(svn_boolean_t *is_rolled_out,
1309 svn_wc_context_t *wc_ctx,
1310 svn_wc__committable_external_info_t *xinfo,
1311 apr_pool_t *scratch_pool)
1312 {
1313 const char *repos_relpath;
1314 const char *repos_root_url;
1315 svn_error_t *err;
1316
1317 *is_rolled_out = FALSE;
1318
1319 err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
1320 &repos_root_url, NULL, NULL, NULL, NULL,
1321 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1322 wc_ctx->db, xinfo->local_abspath,
1323 scratch_pool, scratch_pool);
1324
1325 if (err)
1326 {
1327 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1328 {
1329 svn_error_clear(err);
1330 return SVN_NO_ERROR;
1331 }
1332 SVN_ERR(err);
1333 }
1334
1335 *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 &&
1336 strcmp(xinfo->repos_relpath, repos_relpath) == 0);
1337 return SVN_NO_ERROR;
1338 }
1339
1340 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)1341 svn_wc__committable_externals_below(apr_array_header_t **externals,
1342 svn_wc_context_t *wc_ctx,
1343 const char *local_abspath,
1344 svn_depth_t depth,
1345 apr_pool_t *result_pool,
1346 apr_pool_t *scratch_pool)
1347 {
1348 apr_array_header_t *orig_externals;
1349 int i;
1350 apr_pool_t *iterpool;
1351
1352 /* For svn_depth_files, this also fetches dirs. They are filtered later. */
1353 SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
1354 wc_ctx->db,
1355 local_abspath,
1356 depth != svn_depth_infinity,
1357 result_pool, scratch_pool));
1358
1359 if (orig_externals == NULL)
1360 return SVN_NO_ERROR;
1361
1362 iterpool = svn_pool_create(scratch_pool);
1363
1364 for (i = 0; i < orig_externals->nelts; i++)
1365 {
1366 svn_boolean_t is_rolled_out;
1367
1368 svn_wc__committable_external_info_t *xinfo =
1369 APR_ARRAY_IDX(orig_externals, i,
1370 svn_wc__committable_external_info_t *);
1371
1372 /* Discard dirs for svn_depth_files (s.a.). */
1373 if (depth == svn_depth_files
1374 && xinfo->kind == svn_node_dir)
1375 continue;
1376
1377 svn_pool_clear(iterpool);
1378
1379 /* Discard those externals that are not currently checked out. */
1380 SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo,
1381 iterpool));
1382 if (! is_rolled_out)
1383 continue;
1384
1385 if (*externals == NULL)
1386 *externals = apr_array_make(
1387 result_pool, 0,
1388 sizeof(svn_wc__committable_external_info_t *));
1389
1390 APR_ARRAY_PUSH(*externals,
1391 svn_wc__committable_external_info_t *) = xinfo;
1392
1393 if (depth != svn_depth_infinity)
1394 continue;
1395
1396 /* Are there any nested externals? */
1397 SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
1398 xinfo->local_abspath,
1399 svn_depth_infinity,
1400 result_pool, iterpool));
1401 }
1402
1403 return SVN_NO_ERROR;
1404 }
1405
1406 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)1407 svn_wc__externals_defined_below(apr_hash_t **externals,
1408 svn_wc_context_t *wc_ctx,
1409 const char *local_abspath,
1410 apr_pool_t *result_pool,
1411 apr_pool_t *scratch_pool)
1412 {
1413 return svn_error_trace(
1414 svn_wc__db_externals_defined_below(externals,
1415 wc_ctx->db, local_abspath,
1416 result_pool, scratch_pool));
1417 }
1418
1419 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)1420 svn_wc__external_register(svn_wc_context_t *wc_ctx,
1421 const char *defining_abspath,
1422 const char *local_abspath,
1423 svn_node_kind_t kind,
1424 const char *repos_root_url,
1425 const char *repos_uuid,
1426 const char *repos_relpath,
1427 svn_revnum_t operational_revision,
1428 svn_revnum_t revision,
1429 apr_pool_t *scratch_pool)
1430 {
1431 SVN_ERR_ASSERT(kind == svn_node_dir);
1432 return svn_error_trace(
1433 svn_wc__db_external_add_dir(wc_ctx->db, local_abspath,
1434 defining_abspath,
1435 repos_root_url,
1436 repos_uuid,
1437 defining_abspath,
1438 repos_relpath,
1439 operational_revision,
1440 revision,
1441 NULL,
1442 scratch_pool));
1443 }
1444
1445 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)1446 svn_wc__external_remove(svn_wc_context_t *wc_ctx,
1447 const char *wri_abspath,
1448 const char *local_abspath,
1449 svn_boolean_t declaration_only,
1450 svn_cancel_func_t cancel_func,
1451 void *cancel_baton,
1452 apr_pool_t *scratch_pool)
1453 {
1454 svn_wc__db_status_t status;
1455 svn_node_kind_t kind;
1456
1457 SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL,
1458 NULL, NULL,
1459 wc_ctx->db, local_abspath, wri_abspath,
1460 scratch_pool, scratch_pool));
1461
1462 SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath,
1463 NULL, scratch_pool));
1464
1465 if (declaration_only)
1466 return SVN_NO_ERROR;
1467
1468 if (kind == svn_node_dir)
1469 SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath,
1470 TRUE, TRUE,
1471 cancel_func, cancel_baton,
1472 scratch_pool));
1473 else
1474 {
1475 SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
1476 FALSE, TRUE, FALSE,
1477 0,
1478 NULL, NULL, scratch_pool));
1479 SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
1480 cancel_func, cancel_baton,
1481 scratch_pool));
1482 }
1483
1484 return SVN_NO_ERROR;
1485 }
1486
1487 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)1488 svn_wc__externals_gather_definitions(apr_hash_t **externals,
1489 apr_hash_t **depths,
1490 svn_wc_context_t *wc_ctx,
1491 const char *local_abspath,
1492 svn_depth_t depth,
1493 apr_pool_t *result_pool,
1494 apr_pool_t *scratch_pool)
1495 {
1496 if (depth == svn_depth_infinity
1497 || depth == svn_depth_unknown)
1498 {
1499 return svn_error_trace(
1500 svn_wc__db_externals_gather_definitions(externals, depths,
1501 wc_ctx->db, local_abspath,
1502 result_pool, scratch_pool));
1503 }
1504 else
1505 {
1506 const svn_string_t *value;
1507 svn_error_t *err;
1508 *externals = apr_hash_make(result_pool);
1509
1510 local_abspath = apr_pstrdup(result_pool, local_abspath);
1511
1512 err = svn_wc_prop_get2(&value, wc_ctx, local_abspath,
1513 SVN_PROP_EXTERNALS, result_pool, scratch_pool);
1514
1515 if (err)
1516 {
1517 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1518 return svn_error_trace(err);
1519
1520 svn_error_clear(err);
1521 value = NULL;
1522 }
1523
1524 if (value)
1525 svn_hash_sets(*externals, local_abspath, value->data);
1526
1527 if (value && depths)
1528 {
1529 svn_depth_t node_depth;
1530 *depths = apr_hash_make(result_pool);
1531
1532 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
1533 NULL, NULL, NULL, &node_depth, NULL,
1534 NULL, NULL, NULL, NULL, NULL, NULL,
1535 NULL, NULL, NULL, NULL, NULL, NULL,
1536 NULL, NULL, NULL, NULL,
1537 wc_ctx->db, local_abspath,
1538 scratch_pool, scratch_pool));
1539
1540 svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth));
1541 }
1542
1543 return SVN_NO_ERROR;
1544 }
1545 }
1546
1547 svn_error_t *
svn_wc__close_db(const char * external_abspath,svn_wc_context_t * wc_ctx,apr_pool_t * scratch_pool)1548 svn_wc__close_db(const char *external_abspath,
1549 svn_wc_context_t *wc_ctx,
1550 apr_pool_t *scratch_pool)
1551 {
1552 SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath,
1553 scratch_pool));
1554 return SVN_NO_ERROR;
1555 }
1556
1557 /* Return the scheme of @a uri in @a scheme allocated from @a pool.
1558 If @a uri does not appear to be a valid URI, then @a scheme will
1559 not be updated. */
1560 static svn_error_t *
uri_scheme(const char ** scheme,const char * uri,apr_pool_t * pool)1561 uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
1562 {
1563 apr_size_t i;
1564
1565 for (i = 0; uri[i] && uri[i] != ':'; ++i)
1566 if (uri[i] == '/')
1567 goto error;
1568
1569 if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
1570 {
1571 *scheme = apr_pstrmemdup(pool, uri, i);
1572 return SVN_NO_ERROR;
1573 }
1574
1575 error:
1576 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1577 _("URL '%s' does not begin with a scheme"),
1578 uri);
1579 }
1580
1581 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)1582 svn_wc__resolve_relative_external_url(const char **resolved_url,
1583 const svn_wc_external_item2_t *item,
1584 const char *repos_root_url,
1585 const char *parent_dir_url,
1586 apr_pool_t *result_pool,
1587 apr_pool_t *scratch_pool)
1588 {
1589 const char *url = item->url;
1590 apr_uri_t parent_dir_uri;
1591 apr_status_t status;
1592
1593 *resolved_url = item->url;
1594
1595 /* If the URL is already absolute, there is nothing to do. */
1596 if (svn_path_is_url(url))
1597 {
1598 /* "http://server/path" */
1599 *resolved_url = svn_uri_canonicalize(url, result_pool);
1600 return SVN_NO_ERROR;
1601 }
1602
1603 if (url[0] == '/')
1604 {
1605 /* "/path", "//path", and "///path" */
1606 int num_leading_slashes = 1;
1607 if (url[1] == '/')
1608 {
1609 num_leading_slashes++;
1610 if (url[2] == '/')
1611 num_leading_slashes++;
1612 }
1613
1614 /* "//schema-relative" and in some cases "///schema-relative".
1615 This last format is supported on file:// schema relative. */
1616 url = apr_pstrcat(scratch_pool,
1617 apr_pstrndup(scratch_pool, url, num_leading_slashes),
1618 svn_relpath_canonicalize(url + num_leading_slashes,
1619 scratch_pool),
1620 SVN_VA_NULL);
1621 }
1622 else
1623 {
1624 /* "^/path" and "../path" */
1625 url = svn_relpath_canonicalize(url, scratch_pool);
1626 }
1627
1628 /* Parse the parent directory URL into its parts. */
1629 status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
1630 if (status)
1631 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1632 _("Illegal parent directory URL '%s'"),
1633 parent_dir_url);
1634
1635 /* If the parent directory URL is at the server root, then the URL
1636 may have no / after the hostname so apr_uri_parse() will leave
1637 the URL's path as NULL. */
1638 if (! parent_dir_uri.path)
1639 parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1640 parent_dir_uri.query = NULL;
1641 parent_dir_uri.fragment = NULL;
1642
1643 /* Handle URLs relative to the current directory or to the
1644 repository root. The backpaths may only remove path elements,
1645 not the hostname. This allows an external to refer to another
1646 repository in the same server relative to the location of this
1647 repository, say using SVNParentPath. */
1648 if ((0 == strncmp("../", url, 3)) ||
1649 (0 == strncmp("^/", url, 2)))
1650 {
1651 apr_array_header_t *base_components;
1652 apr_array_header_t *relative_components;
1653 int i;
1654
1655 /* Decompose either the parent directory's URL path or the
1656 repository root's URL path into components. */
1657 if (0 == strncmp("../", url, 3))
1658 {
1659 base_components = svn_path_decompose(parent_dir_uri.path,
1660 scratch_pool);
1661 relative_components = svn_path_decompose(url, scratch_pool);
1662 }
1663 else
1664 {
1665 apr_uri_t repos_root_uri;
1666
1667 status = apr_uri_parse(scratch_pool, repos_root_url,
1668 &repos_root_uri);
1669 if (status)
1670 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1671 _("Illegal repository root URL '%s'"),
1672 repos_root_url);
1673
1674 /* If the repository root URL is at the server root, then
1675 the URL may have no / after the hostname so
1676 apr_uri_parse() will leave the URL's path as NULL. */
1677 if (! repos_root_uri.path)
1678 repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1679
1680 base_components = svn_path_decompose(repos_root_uri.path,
1681 scratch_pool);
1682 relative_components = svn_path_decompose(url + 2, scratch_pool);
1683 }
1684
1685 for (i = 0; i < relative_components->nelts; ++i)
1686 {
1687 const char *component = APR_ARRAY_IDX(relative_components,
1688 i,
1689 const char *);
1690 if (0 == strcmp("..", component))
1691 {
1692 /* Constructing the final absolute URL together with
1693 apr_uri_unparse() requires that the path be absolute,
1694 so only pop a component if the component being popped
1695 is not the component for the root directory. */
1696 if (base_components->nelts > 1)
1697 apr_array_pop(base_components);
1698 }
1699 else
1700 APR_ARRAY_PUSH(base_components, const char *) = component;
1701 }
1702
1703 parent_dir_uri.path = (char *)svn_path_compose(base_components,
1704 scratch_pool);
1705 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1706 &parent_dir_uri, 0),
1707 result_pool);
1708 return SVN_NO_ERROR;
1709 }
1710
1711 /* The remaining URLs are relative to either the scheme or server root
1712 and can only refer to locations inside that scope, so backpaths are
1713 not allowed. */
1714 if (svn_path_is_backpath_present(url))
1715 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1716 _("The external relative URL '%s' cannot have "
1717 "backpaths, i.e. '..'"),
1718 item->url);
1719
1720 /* Relative to the scheme: Build a new URL from the parts we know. */
1721 if (0 == strncmp("//", url, 2))
1722 {
1723 const char *scheme;
1724
1725 SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
1726 *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
1727 ":", url, SVN_VA_NULL),
1728 result_pool);
1729 return SVN_NO_ERROR;
1730 }
1731
1732 /* Relative to the server root: Just replace the path portion of the
1733 parent's URL. */
1734 if (url[0] == '/')
1735 {
1736 parent_dir_uri.path = (char *)url;
1737 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1738 &parent_dir_uri, 0),
1739 result_pool);
1740 return SVN_NO_ERROR;
1741 }
1742
1743 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1744 _("Unrecognized format for the relative external "
1745 "URL '%s'"),
1746 item->url);
1747 }
1748