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 *url;
409  const char *repos_root_url;
410  const char *repos_uuid;
411
412  const char *record_ancestor_abspath;
413  const char *recorded_repos_relpath;
414  svn_revnum_t recorded_peg_revision;
415  svn_revnum_t recorded_revision;
416
417  /* Introducing a new file external */
418  svn_boolean_t added;
419
420  svn_wc_conflict_resolver_func2_t conflict_func;
421  void *conflict_baton;
422  svn_cancel_func_t cancel_func;
423  void *cancel_baton;
424  svn_wc_notify_func2_t notify_func;
425  void *notify_baton;
426
427  svn_revnum_t *target_revision;
428
429  /* What was there before the update */
430  svn_revnum_t original_revision;
431  const svn_checksum_t *original_checksum;
432
433  /* What we are installing now */
434  const char *new_pristine_abspath;
435  svn_checksum_t *new_sha1_checksum;
436  svn_checksum_t *new_md5_checksum;
437
438  /* List of incoming propchanges */
439  apr_array_header_t *propchanges;
440
441  /* Array of svn_prop_inherited_item_t * structures representing the
442     properties inherited by the base node at LOCAL_ABSPATH. */
443  apr_array_header_t *iprops;
444
445  /* The last change information */
446  svn_revnum_t changed_rev;
447  apr_time_t changed_date;
448  const char *changed_author;
449
450  svn_boolean_t had_props;
451
452  svn_boolean_t file_closed;
453};
454
455/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
456static svn_error_t *
457set_target_revision(void *edit_baton,
458                     svn_revnum_t target_revision,
459                     apr_pool_t *pool)
460{
461  struct edit_baton *eb = edit_baton;
462
463  *eb->target_revision = target_revision;
464
465  return SVN_NO_ERROR;
466}
467
468/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
469static svn_error_t *
470open_root(void *edit_baton,
471          svn_revnum_t base_revision,
472          apr_pool_t *dir_pool,
473          void **root_baton)
474{
475  *root_baton = edit_baton;
476  return SVN_NO_ERROR;
477}
478
479/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
480static svn_error_t *
481add_file(const char *path,
482         void *parent_baton,
483         const char *copyfrom_path,
484         svn_revnum_t copyfrom_revision,
485         apr_pool_t *file_pool,
486         void **file_baton)
487{
488  struct edit_baton *eb = parent_baton;
489  if (strcmp(path, eb->name))
490      return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
491                               _("This editor can only update '%s'"),
492                               svn_dirent_local_style(eb->local_abspath,
493                                                      file_pool));
494
495  *file_baton = eb;
496  eb->original_revision = SVN_INVALID_REVNUM;
497  eb->added = TRUE;
498
499  return SVN_NO_ERROR;
500}
501
502/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
503static svn_error_t *
504open_file(const char *path,
505          void *parent_baton,
506          svn_revnum_t base_revision,
507          apr_pool_t *file_pool,
508          void **file_baton)
509{
510  struct edit_baton *eb = parent_baton;
511  svn_node_kind_t kind;
512  if (strcmp(path, eb->name))
513      return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
514                               _("This editor can only update '%s'"),
515                               svn_dirent_local_style(eb->local_abspath,
516                                                      file_pool));
517
518  *file_baton = eb;
519  SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision,
520                                   NULL, NULL, NULL, &eb->changed_rev,
521                                   &eb->changed_date, &eb->changed_author,
522                                   NULL, &eb->original_checksum, NULL, NULL,
523                                   &eb->had_props, NULL, NULL,
524                                   eb->db, eb->local_abspath,
525                                   eb->pool, file_pool));
526
527  if (kind != svn_node_file)
528    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
529                               _("Node '%s' is no existing file external"),
530                               svn_dirent_local_style(eb->local_abspath,
531                                                      file_pool));
532  return SVN_NO_ERROR;
533}
534
535/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
536static svn_error_t *
537apply_textdelta(void *file_baton,
538                const char *base_checksum_digest,
539                apr_pool_t *pool,
540                svn_txdelta_window_handler_t *handler,
541                void **handler_baton)
542{
543  struct edit_baton *eb = file_baton;
544  svn_stream_t *src_stream;
545  svn_stream_t *dest_stream;
546
547  if (eb->original_checksum)
548    {
549      if (base_checksum_digest)
550        {
551          svn_checksum_t *expected_checksum;
552          const svn_checksum_t *original_md5;
553
554          SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
555                                         base_checksum_digest, pool));
556
557          if (eb->original_checksum->kind != svn_checksum_md5)
558            SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5,
559                                                eb->db, eb->wri_abspath,
560                                                eb->original_checksum,
561                                                pool, pool));
562          else
563            original_md5 = eb->original_checksum;
564
565          if (!svn_checksum_match(expected_checksum, original_md5))
566            return svn_error_trace(svn_checksum_mismatch_err(
567                                    expected_checksum,
568                                    original_md5,
569                                    pool,
570                                    _("Base checksum mismatch for '%s'"),
571                                    svn_dirent_local_style(eb->local_abspath,
572                                                           pool)));
573        }
574
575      SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db,
576                                       eb->wri_abspath, eb->original_checksum,
577                                       pool, pool));
578    }
579  else
580    src_stream = svn_stream_empty(pool);
581
582  SVN_ERR(svn_wc__open_writable_base(&dest_stream, &eb->new_pristine_abspath,
583                                     &eb->new_md5_checksum,
584                                     &eb->new_sha1_checksum,
585                                     eb->db, eb->wri_abspath,
586                                     eb->pool, pool));
587
588  svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool,
589                    handler, handler_baton);
590
591  return SVN_NO_ERROR;
592}
593
594/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
595static svn_error_t *
596change_file_prop(void *file_baton,
597                 const char *name,
598                 const svn_string_t *value,
599                 apr_pool_t *pool)
600{
601  struct edit_baton *eb = file_baton;
602  svn_prop_t *propchange;
603
604  propchange = apr_array_push(eb->propchanges);
605  propchange->name = apr_pstrdup(eb->pool, name);
606  propchange->value = value ? svn_string_dup(value, eb->pool) : NULL;
607
608  return SVN_NO_ERROR;
609}
610
611/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
612static svn_error_t *
613close_file(void *file_baton,
614           const char *expected_md5_digest,
615           apr_pool_t *pool)
616{
617  struct edit_baton *eb = file_baton;
618  svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
619  svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown;
620  svn_boolean_t obstructed = FALSE;
621
622  eb->file_closed = TRUE; /* We bump the revision here */
623
624  /* Check the checksum, if provided */
625  if (expected_md5_digest)
626    {
627      svn_checksum_t *expected_md5_checksum;
628      const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum;
629
630      SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
631                                     expected_md5_digest, pool));
632
633      if (actual_md5_checksum == NULL)
634        {
635          actual_md5_checksum = eb->original_checksum;
636
637          if (actual_md5_checksum != NULL
638              && actual_md5_checksum->kind != svn_checksum_md5)
639            {
640              SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum,
641                                                  eb->db, eb->wri_abspath,
642                                                  actual_md5_checksum,
643                                                  pool, pool));
644            }
645        }
646
647      if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum))
648        return svn_checksum_mismatch_err(
649                        expected_md5_checksum,
650                        actual_md5_checksum, pool,
651                        _("Checksum mismatch for '%s'"),
652                        svn_dirent_local_style(eb->local_abspath, pool));
653    }
654
655  /* First move the file in the pristine store; this hands over the cleanup
656     behavior to the pristine store. */
657  if (eb->new_sha1_checksum)
658    {
659      SVN_ERR(svn_wc__db_pristine_install(eb->db, eb->new_pristine_abspath,
660                                          eb->new_sha1_checksum,
661                                          eb->new_md5_checksum, pool));
662
663      eb->new_pristine_abspath = NULL;
664    }
665
666  /* Merge the changes */
667  {
668    svn_skel_t *all_work_items = NULL;
669    svn_skel_t *conflict_skel = NULL;
670    svn_skel_t *work_item;
671    apr_hash_t *base_props = NULL;
672    apr_hash_t *actual_props = NULL;
673    apr_hash_t *new_pristine_props = NULL;
674    apr_hash_t *new_actual_props = NULL;
675    apr_hash_t *new_dav_props = NULL;
676    const svn_checksum_t *new_checksum = NULL;
677    const svn_checksum_t *original_checksum = NULL;
678
679    svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision);
680    const char *repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url,
681                                                      eb->url, pool);
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                                    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                                    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                        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      || eb->iprops)
950    {
951      apr_hash_t *wcroot_iprops = NULL;
952
953      if (eb->iprops)
954        {
955          wcroot_iprops = apr_hash_make(pool);
956          svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops);
957        }
958
959      /* The node wasn't updated, so we just have to bump its revision */
960      SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
961                                                       eb->local_abspath,
962                                                       svn_depth_infinity,
963                                                       NULL, NULL, NULL,
964                                                       *eb->target_revision,
965                                                       apr_hash_make(pool),
966                                                       wcroot_iprops,
967                                                       eb->notify_func,
968                                                       eb->notify_baton,
969                                                       pool));
970    }
971
972  return SVN_NO_ERROR;
973}
974
975svn_error_t *
976svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
977                                 void **edit_baton,
978                                 svn_revnum_t *target_revision,
979                                 svn_wc_context_t *wc_ctx,
980                                 const char *local_abspath,
981                                 const char *wri_abspath,
982                                 const char *url,
983                                 const char *repos_root_url,
984                                 const char *repos_uuid,
985                                 apr_array_header_t *iprops,
986                                 svn_boolean_t use_commit_times,
987                                 const char *diff3_cmd,
988                                 const apr_array_header_t *preserved_exts,
989                                 const char *record_ancestor_abspath,
990                                 const char *recorded_url,
991                                 const svn_opt_revision_t *recorded_peg_rev,
992                                 const svn_opt_revision_t *recorded_rev,
993                                 svn_wc_conflict_resolver_func2_t conflict_func,
994                                 void *conflict_baton,
995                                 svn_cancel_func_t cancel_func,
996                                 void *cancel_baton,
997                                 svn_wc_notify_func2_t notify_func,
998                                 void *notify_baton,
999                                 apr_pool_t *result_pool,
1000                                 apr_pool_t *scratch_pool)
1001{
1002  svn_wc__db_t *db = wc_ctx->db;
1003  apr_pool_t *edit_pool = result_pool;
1004  struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb));
1005  svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
1006
1007  eb->pool = edit_pool;
1008  eb->db = db;
1009  eb->local_abspath = apr_pstrdup(edit_pool, local_abspath);
1010  if (wri_abspath)
1011    eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath);
1012  else
1013    eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool);
1014  eb->name = svn_dirent_basename(eb->local_abspath, NULL);
1015  eb->target_revision = target_revision;
1016
1017  eb->url = apr_pstrdup(edit_pool, url);
1018  eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
1019  eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
1020
1021  eb->iprops = iprops;
1022
1023  eb->use_commit_times = use_commit_times;
1024  eb->ext_patterns = preserved_exts;
1025  eb->diff3cmd = diff3_cmd;
1026
1027  eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath);
1028  eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url,
1029                                                     edit_pool);
1030
1031  eb->changed_rev = SVN_INVALID_REVNUM;
1032
1033  if (recorded_peg_rev->kind == svn_opt_revision_number)
1034    eb->recorded_peg_revision = recorded_peg_rev->value.number;
1035  else
1036    eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1037
1038  if (recorded_rev->kind == svn_opt_revision_number)
1039    eb->recorded_revision = recorded_rev->value.number;
1040  else
1041    eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1042
1043  eb->conflict_func = conflict_func;
1044  eb->conflict_baton = conflict_baton;
1045  eb->cancel_func = cancel_func;
1046  eb->cancel_baton = cancel_baton;
1047  eb->notify_func = notify_func;
1048  eb->notify_baton = notify_baton;
1049
1050  eb->propchanges  = apr_array_make(edit_pool, 1, sizeof(svn_prop_t));
1051
1052  tree_editor->open_root = open_root;
1053  tree_editor->set_target_revision = set_target_revision;
1054  tree_editor->add_file = add_file;
1055  tree_editor->open_file = open_file;
1056  tree_editor->apply_textdelta = apply_textdelta;
1057  tree_editor->change_file_prop = change_file_prop;
1058  tree_editor->close_file = close_file;
1059  tree_editor->close_edit = close_edit;
1060
1061  return svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1062                                           tree_editor, eb,
1063                                           editor, edit_baton,
1064                                           result_pool);
1065}
1066
1067svn_error_t *
1068svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
1069                            const char *local_abspath,
1070                            const svn_ra_reporter3_t *reporter,
1071                            void *report_baton,
1072                            svn_boolean_t restore_files,
1073                            svn_boolean_t use_commit_times,
1074                            svn_cancel_func_t cancel_func,
1075                            void *cancel_baton,
1076                            svn_wc_notify_func2_t notify_func,
1077                            void *notify_baton,
1078                            apr_pool_t *scratch_pool)
1079{
1080  svn_wc__db_t *db = wc_ctx->db;
1081  svn_error_t *err;
1082  svn_node_kind_t kind;
1083  svn_wc__db_lock_t *lock;
1084  svn_revnum_t revision;
1085  const char *repos_root_url;
1086  const char *repos_relpath;
1087  svn_boolean_t update_root;
1088
1089  err = svn_wc__db_base_get_info(NULL, &kind, &revision,
1090                                 &repos_relpath, &repos_root_url, NULL, NULL,
1091                                 NULL, NULL, NULL, NULL, NULL, &lock,
1092                                 NULL, NULL, &update_root,
1093                                 db, local_abspath,
1094                                 scratch_pool, scratch_pool);
1095
1096  if (err
1097      || kind == svn_node_dir
1098      || !update_root)
1099    {
1100      if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1101        return svn_error_trace(err);
1102
1103      svn_error_clear(err);
1104
1105      /* We don't know about this node, so all we have to do is tell
1106         the reporter that we don't know this node.
1107
1108         But first we have to start the report by sending some basic
1109         information for the root. */
1110
1111      SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity,
1112                                 FALSE, NULL, scratch_pool));
1113      SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
1114
1115      /* Finish the report, which causes the update editor to be
1116         driven. */
1117      SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1118
1119      return SVN_NO_ERROR;
1120    }
1121  else
1122    {
1123      if (restore_files)
1124        {
1125          svn_node_kind_t disk_kind;
1126          SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
1127
1128          if (disk_kind == svn_node_none)
1129            {
1130              err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times,
1131                                   scratch_pool);
1132
1133              if (err)
1134                {
1135                  if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1136                    return svn_error_trace(err);
1137
1138                  svn_error_clear(err);
1139                }
1140            }
1141        }
1142
1143      /* Report that we know the path */
1144      SVN_ERR(reporter->set_path(report_baton, "", revision,
1145                                 svn_depth_infinity, FALSE, NULL,
1146                                 scratch_pool));
1147
1148      /* For compatibility with the normal update editor report we report
1149         the target as switched.
1150
1151         ### We can probably report a parent url and unswitched later */
1152      SVN_ERR(reporter->link_path(report_baton, "",
1153                                  svn_path_url_add_component2(repos_root_url,
1154                                                              repos_relpath,
1155                                                              scratch_pool),
1156                                  revision,
1157                                  svn_depth_infinity,
1158                                  FALSE /* start_empty*/,
1159                                  lock ? lock->token : NULL,
1160                                  scratch_pool));
1161    }
1162
1163  return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
1164}
1165
1166svn_error_t *
1167svn_wc__read_external_info(svn_node_kind_t *external_kind,
1168                           const char **defining_abspath,
1169                           const char **defining_url,
1170                           svn_revnum_t *defining_operational_revision,
1171                           svn_revnum_t *defining_revision,
1172                           svn_wc_context_t *wc_ctx,
1173                           const char *wri_abspath,
1174                           const char *local_abspath,
1175                           svn_boolean_t ignore_enoent,
1176                           apr_pool_t *result_pool,
1177                           apr_pool_t *scratch_pool)
1178{
1179  const char *repos_root_url;
1180  svn_wc__db_status_t status;
1181  svn_node_kind_t kind;
1182  svn_error_t *err;
1183
1184  err = svn_wc__db_external_read(&status, &kind, defining_abspath,
1185                                 defining_url ? &repos_root_url : NULL, NULL,
1186                                 defining_url, defining_operational_revision,
1187                                 defining_revision,
1188                                 wc_ctx->db, local_abspath, wri_abspath,
1189                                 result_pool, scratch_pool);
1190
1191  if (err)
1192    {
1193      if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent)
1194        return svn_error_trace(err);
1195
1196      svn_error_clear(err);
1197
1198      if (external_kind)
1199        *external_kind = svn_node_none;
1200
1201      if (defining_abspath)
1202        *defining_abspath = NULL;
1203
1204      if (defining_url)
1205        *defining_url = NULL;
1206
1207      if (defining_operational_revision)
1208        *defining_operational_revision = SVN_INVALID_REVNUM;
1209
1210      if (defining_revision)
1211        *defining_revision = SVN_INVALID_REVNUM;
1212
1213      return SVN_NO_ERROR;
1214    }
1215
1216  if (external_kind)
1217    {
1218      if (status != svn_wc__db_status_normal)
1219        *external_kind = svn_node_unknown;
1220      else
1221        switch(kind)
1222          {
1223            case svn_node_file:
1224            case svn_node_symlink:
1225              *external_kind = svn_node_file;
1226              break;
1227            case svn_node_dir:
1228              *external_kind = svn_node_dir;
1229              break;
1230            default:
1231              *external_kind = svn_node_none;
1232          }
1233    }
1234
1235  if (defining_url && *defining_url)
1236    *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url,
1237                                                result_pool);
1238
1239  return SVN_NO_ERROR;
1240}
1241
1242/* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and
1243 * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and
1244 * XINFO->REPOS_RELPATH.  All allocations are made in SCRATCH_POOL. */
1245static svn_error_t *
1246is_external_rolled_out(svn_boolean_t *is_rolled_out,
1247                       svn_wc_context_t *wc_ctx,
1248                       svn_wc__committable_external_info_t *xinfo,
1249                       apr_pool_t *scratch_pool)
1250{
1251  const char *repos_relpath;
1252  const char *repos_root_url;
1253  svn_error_t *err;
1254
1255  *is_rolled_out = FALSE;
1256
1257  err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
1258                                 &repos_root_url, NULL, NULL, NULL, NULL,
1259                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1260                                 wc_ctx->db, xinfo->local_abspath,
1261                                 scratch_pool, scratch_pool);
1262
1263  if (err)
1264    {
1265      if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1266        {
1267          svn_error_clear(err);
1268          return SVN_NO_ERROR;
1269        }
1270      SVN_ERR(err);
1271    }
1272
1273  *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 &&
1274                    strcmp(xinfo->repos_relpath, repos_relpath) == 0);
1275  return SVN_NO_ERROR;
1276}
1277
1278svn_error_t *
1279svn_wc__committable_externals_below(apr_array_header_t **externals,
1280                                    svn_wc_context_t *wc_ctx,
1281                                    const char *local_abspath,
1282                                    svn_depth_t depth,
1283                                    apr_pool_t *result_pool,
1284                                    apr_pool_t *scratch_pool)
1285{
1286  apr_array_header_t *orig_externals;
1287  int i;
1288  apr_pool_t *iterpool;
1289
1290  /* For svn_depth_files, this also fetches dirs. They are filtered later. */
1291  SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
1292                                                 wc_ctx->db,
1293                                                 local_abspath,
1294                                                 depth != svn_depth_infinity,
1295                                                 result_pool, scratch_pool));
1296
1297  if (orig_externals == NULL)
1298    return SVN_NO_ERROR;
1299
1300  iterpool = svn_pool_create(scratch_pool);
1301
1302  for (i = 0; i < orig_externals->nelts; i++)
1303    {
1304      svn_boolean_t is_rolled_out;
1305
1306      svn_wc__committable_external_info_t *xinfo =
1307        APR_ARRAY_IDX(orig_externals, i,
1308                      svn_wc__committable_external_info_t *);
1309
1310      /* Discard dirs for svn_depth_files (s.a.). */
1311      if (depth == svn_depth_files
1312          && xinfo->kind == svn_node_dir)
1313        continue;
1314
1315      svn_pool_clear(iterpool);
1316
1317      /* Discard those externals that are not currently checked out. */
1318      SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo,
1319                                     iterpool));
1320      if (! is_rolled_out)
1321        continue;
1322
1323      if (*externals == NULL)
1324        *externals = apr_array_make(
1325                               result_pool, 0,
1326                               sizeof(svn_wc__committable_external_info_t *));
1327
1328      APR_ARRAY_PUSH(*externals,
1329                     svn_wc__committable_external_info_t *) = xinfo;
1330
1331      if (depth != svn_depth_infinity)
1332        continue;
1333
1334      /* Are there any nested externals? */
1335      SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
1336                                                  xinfo->local_abspath,
1337                                                  svn_depth_infinity,
1338                                                  result_pool, iterpool));
1339    }
1340
1341  return SVN_NO_ERROR;
1342}
1343
1344svn_error_t *
1345svn_wc__externals_defined_below(apr_hash_t **externals,
1346                                svn_wc_context_t *wc_ctx,
1347                                const char *local_abspath,
1348                                apr_pool_t *result_pool,
1349                                apr_pool_t *scratch_pool)
1350{
1351  return svn_error_trace(
1352            svn_wc__db_externals_defined_below(externals,
1353                                               wc_ctx->db, local_abspath,
1354                                               result_pool, scratch_pool));
1355}
1356
1357svn_error_t *
1358svn_wc__external_register(svn_wc_context_t *wc_ctx,
1359                          const char *defining_abspath,
1360                          const char *local_abspath,
1361                          svn_node_kind_t kind,
1362                          const char *repos_root_url,
1363                          const char *repos_uuid,
1364                          const char *repos_relpath,
1365                          svn_revnum_t operational_revision,
1366                          svn_revnum_t revision,
1367                          apr_pool_t *scratch_pool)
1368{
1369  SVN_ERR_ASSERT(kind == svn_node_dir);
1370  return svn_error_trace(
1371            svn_wc__db_external_add_dir(wc_ctx->db, local_abspath,
1372                                        defining_abspath,
1373                                        repos_root_url,
1374                                        repos_uuid,
1375                                        defining_abspath,
1376                                        repos_relpath,
1377                                        operational_revision,
1378                                        revision,
1379                                        NULL,
1380                                        scratch_pool));
1381}
1382
1383svn_error_t *
1384svn_wc__external_remove(svn_wc_context_t *wc_ctx,
1385                        const char *wri_abspath,
1386                        const char *local_abspath,
1387                        svn_boolean_t declaration_only,
1388                        svn_cancel_func_t cancel_func,
1389                        void *cancel_baton,
1390                        apr_pool_t *scratch_pool)
1391{
1392  svn_wc__db_status_t status;
1393  svn_node_kind_t kind;
1394
1395  SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL,
1396                                   NULL, NULL,
1397                                   wc_ctx->db, local_abspath, wri_abspath,
1398                                   scratch_pool, scratch_pool));
1399
1400  SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath,
1401                                     NULL, scratch_pool));
1402
1403  if (declaration_only)
1404    return SVN_NO_ERROR;
1405
1406  if (kind == svn_node_dir)
1407    SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath,
1408                                                 TRUE, TRUE,
1409                                                 cancel_func, cancel_baton,
1410                                                 scratch_pool));
1411  else
1412    {
1413      SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
1414                                     FALSE /* keep_as_working */,
1415                                     TRUE /* queue_deletes */,
1416                                     FALSE /* remove_locks */,
1417                                     SVN_INVALID_REVNUM,
1418                                     NULL, NULL, scratch_pool));
1419      SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
1420                             cancel_func, cancel_baton,
1421                             scratch_pool));
1422    }
1423
1424  return SVN_NO_ERROR;
1425}
1426
1427svn_error_t *
1428svn_wc__externals_gather_definitions(apr_hash_t **externals,
1429                                     apr_hash_t **depths,
1430                                     svn_wc_context_t *wc_ctx,
1431                                     const char *local_abspath,
1432                                     svn_depth_t depth,
1433                                     apr_pool_t *result_pool,
1434                                     apr_pool_t *scratch_pool)
1435{
1436  if (depth == svn_depth_infinity
1437      || depth == svn_depth_unknown)
1438    {
1439      return svn_error_trace(
1440        svn_wc__db_externals_gather_definitions(externals, depths,
1441                                                wc_ctx->db, local_abspath,
1442                                                result_pool, scratch_pool));
1443    }
1444  else
1445    {
1446      const svn_string_t *value;
1447      svn_error_t *err;
1448      *externals = apr_hash_make(result_pool);
1449
1450      local_abspath = apr_pstrdup(result_pool, local_abspath);
1451
1452      err = svn_wc_prop_get2(&value, wc_ctx, local_abspath,
1453                             SVN_PROP_EXTERNALS, result_pool, scratch_pool);
1454
1455      if (err)
1456        {
1457          if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1458            return svn_error_trace(err);
1459
1460          svn_error_clear(err);
1461          value = NULL;
1462        }
1463
1464      if (value)
1465        svn_hash_sets(*externals, local_abspath, value->data);
1466
1467      if (value && depths)
1468        {
1469          svn_depth_t node_depth;
1470          *depths = apr_hash_make(result_pool);
1471
1472          SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
1473                                       NULL, NULL, NULL, &node_depth, NULL,
1474                                       NULL, NULL, NULL, NULL, NULL, NULL,
1475                                       NULL, NULL, NULL, NULL, NULL, NULL,
1476                                       NULL, NULL, NULL, NULL,
1477                                       wc_ctx->db, local_abspath,
1478                                       scratch_pool, scratch_pool));
1479
1480          svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth));
1481        }
1482
1483      return SVN_NO_ERROR;
1484    }
1485}
1486
1487svn_error_t *
1488svn_wc__close_db(const char *external_abspath,
1489                 svn_wc_context_t *wc_ctx,
1490                 apr_pool_t *scratch_pool)
1491{
1492  SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath,
1493                               scratch_pool));
1494  return SVN_NO_ERROR;
1495}
1496
1497/* Return the scheme of @a uri in @a scheme allocated from @a pool.
1498   If @a uri does not appear to be a valid URI, then @a scheme will
1499   not be updated.  */
1500static svn_error_t *
1501uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
1502{
1503  apr_size_t i;
1504
1505  for (i = 0; uri[i] && uri[i] != ':'; ++i)
1506    if (uri[i] == '/')
1507      goto error;
1508
1509  if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
1510    {
1511      *scheme = apr_pstrmemdup(pool, uri, i);
1512      return SVN_NO_ERROR;
1513    }
1514
1515error:
1516  return svn_error_createf(SVN_ERR_BAD_URL, 0,
1517                           _("URL '%s' does not begin with a scheme"),
1518                           uri);
1519}
1520
1521svn_error_t *
1522svn_wc__resolve_relative_external_url(const char **resolved_url,
1523                                      const svn_wc_external_item2_t *item,
1524                                      const char *repos_root_url,
1525                                      const char *parent_dir_url,
1526                                      apr_pool_t *result_pool,
1527                                      apr_pool_t *scratch_pool)
1528{
1529  const char *url = item->url;
1530  apr_uri_t parent_dir_uri;
1531  apr_status_t status;
1532
1533  *resolved_url = item->url;
1534
1535  /* If the URL is already absolute, there is nothing to do. */
1536  if (svn_path_is_url(url))
1537    {
1538      /* "http://server/path" */
1539      *resolved_url = svn_uri_canonicalize(url, result_pool);
1540      return SVN_NO_ERROR;
1541    }
1542
1543  if (url[0] == '/')
1544    {
1545      /* "/path", "//path", and "///path" */
1546      int num_leading_slashes = 1;
1547      if (url[1] == '/')
1548        {
1549          num_leading_slashes++;
1550          if (url[2] == '/')
1551            num_leading_slashes++;
1552        }
1553
1554      /* "//schema-relative" and in some cases "///schema-relative".
1555         This last format is supported on file:// schema relative. */
1556      url = apr_pstrcat(scratch_pool,
1557                        apr_pstrndup(scratch_pool, url, num_leading_slashes),
1558                        svn_relpath_canonicalize(url + num_leading_slashes,
1559                                                 scratch_pool),
1560                        (char*)NULL);
1561    }
1562  else
1563    {
1564      /* "^/path" and "../path" */
1565      url = svn_relpath_canonicalize(url, scratch_pool);
1566    }
1567
1568  /* Parse the parent directory URL into its parts. */
1569  status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
1570  if (status)
1571    return svn_error_createf(SVN_ERR_BAD_URL, 0,
1572                             _("Illegal parent directory URL '%s'"),
1573                             parent_dir_url);
1574
1575  /* If the parent directory URL is at the server root, then the URL
1576     may have no / after the hostname so apr_uri_parse() will leave
1577     the URL's path as NULL. */
1578  if (! parent_dir_uri.path)
1579    parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1580  parent_dir_uri.query = NULL;
1581  parent_dir_uri.fragment = NULL;
1582
1583  /* Handle URLs relative to the current directory or to the
1584     repository root.  The backpaths may only remove path elements,
1585     not the hostname.  This allows an external to refer to another
1586     repository in the same server relative to the location of this
1587     repository, say using SVNParentPath. */
1588  if ((0 == strncmp("../", url, 3)) ||
1589      (0 == strncmp("^/", url, 2)))
1590    {
1591      apr_array_header_t *base_components;
1592      apr_array_header_t *relative_components;
1593      int i;
1594
1595      /* Decompose either the parent directory's URL path or the
1596         repository root's URL path into components.  */
1597      if (0 == strncmp("../", url, 3))
1598        {
1599          base_components = svn_path_decompose(parent_dir_uri.path,
1600                                               scratch_pool);
1601          relative_components = svn_path_decompose(url, scratch_pool);
1602        }
1603      else
1604        {
1605          apr_uri_t repos_root_uri;
1606
1607          status = apr_uri_parse(scratch_pool, repos_root_url,
1608                                 &repos_root_uri);
1609          if (status)
1610            return svn_error_createf(SVN_ERR_BAD_URL, 0,
1611                                     _("Illegal repository root URL '%s'"),
1612                                     repos_root_url);
1613
1614          /* If the repository root URL is at the server root, then
1615             the URL may have no / after the hostname so
1616             apr_uri_parse() will leave the URL's path as NULL. */
1617          if (! repos_root_uri.path)
1618            repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1619
1620          base_components = svn_path_decompose(repos_root_uri.path,
1621                                               scratch_pool);
1622          relative_components = svn_path_decompose(url + 2, scratch_pool);
1623        }
1624
1625      for (i = 0; i < relative_components->nelts; ++i)
1626        {
1627          const char *component = APR_ARRAY_IDX(relative_components,
1628                                                i,
1629                                                const char *);
1630          if (0 == strcmp("..", component))
1631            {
1632              /* Constructing the final absolute URL together with
1633                 apr_uri_unparse() requires that the path be absolute,
1634                 so only pop a component if the component being popped
1635                 is not the component for the root directory. */
1636              if (base_components->nelts > 1)
1637                apr_array_pop(base_components);
1638            }
1639          else
1640            APR_ARRAY_PUSH(base_components, const char *) = component;
1641        }
1642
1643      parent_dir_uri.path = (char *)svn_path_compose(base_components,
1644                                                     scratch_pool);
1645      *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1646                                                           &parent_dir_uri, 0),
1647                                       result_pool);
1648      return SVN_NO_ERROR;
1649    }
1650
1651  /* The remaining URLs are relative to either the scheme or server root
1652     and can only refer to locations inside that scope, so backpaths are
1653     not allowed. */
1654  if (svn_path_is_backpath_present(url))
1655    return svn_error_createf(SVN_ERR_BAD_URL, 0,
1656                             _("The external relative URL '%s' cannot have "
1657                               "backpaths, i.e. '..'"),
1658                             item->url);
1659
1660  /* Relative to the scheme: Build a new URL from the parts we know. */
1661  if (0 == strncmp("//", url, 2))
1662    {
1663      const char *scheme;
1664
1665      SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
1666      *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
1667                                                       ":", url, (char *)NULL),
1668                                           result_pool);
1669      return SVN_NO_ERROR;
1670    }
1671
1672  /* Relative to the server root: Just replace the path portion of the
1673     parent's URL. */
1674  if (url[0] == '/')
1675    {
1676      parent_dir_uri.path = (char *)url;
1677      *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1678                                                           &parent_dir_uri, 0),
1679                                           result_pool);
1680      return SVN_NO_ERROR;
1681    }
1682
1683  return svn_error_createf(SVN_ERR_BAD_URL, 0,
1684                           _("Unrecognized format for the relative external "
1685                             "URL '%s'"),
1686                           item->url);
1687}
1688