xref: /NextBSD/contrib/subversion/subversion/libsvn_wc/externals.c (revision 84d351007654069f9643c8e4b4802a7f5f08ee42)
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, &regular_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