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