In the command-line client, forbid
[svn.git] / subversion / libsvn_client / mergeinfo.c
blob589ea7d4dce9dba1865599ed94221326b0b9e510
1 /*
2 * mergeinfo.c : merge history functions for the libsvn_client library
4 * ====================================================================
5 * Copyright (c) 2006-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 #include <apr_pools.h>
20 #include <apr_strings.h>
21 #include <assert.h>
23 #include "svn_pools.h"
24 #include "svn_path.h"
25 #include "svn_string.h"
26 #include "svn_opt.h"
27 #include "svn_error.h"
28 #include "svn_error_codes.h"
29 #include "svn_props.h"
30 #include "svn_mergeinfo.h"
31 #include "svn_sorts.h"
32 #include "svn_ra.h"
33 #include "svn_client.h"
35 #include "private/svn_mergeinfo_private.h"
36 #include "private/svn_wc_private.h"
37 #include "private/svn_ra_private.h"
38 #include "client.h"
39 #include "mergeinfo.h"
40 #include "svn_private_config.h"
44 svn_error_t *
45 svn_client__parse_mergeinfo(apr_hash_t **mergeinfo,
46 const svn_wc_entry_t *entry,
47 const char *wcpath,
48 svn_boolean_t pristine,
49 svn_wc_adm_access_t *adm_access,
50 svn_client_ctx_t *ctx,
51 apr_pool_t *pool)
53 apr_hash_t *props = apr_hash_make(pool);
54 const svn_string_t *propval;
56 /* ### Use svn_wc_prop_get() would actually be sufficient for now.
57 ### DannyB thinks that later we'll need behavior more like
58 ### svn_client__get_prop_from_wc(). */
59 SVN_ERR(svn_client__get_prop_from_wc(props, SVN_PROP_MERGEINFO,
60 wcpath, pristine, entry, adm_access,
61 svn_depth_empty, ctx, pool));
62 propval = apr_hash_get(props, wcpath, APR_HASH_KEY_STRING);
63 if (propval)
64 SVN_ERR(svn_mergeinfo_parse(mergeinfo, propval->data, pool));
65 else
66 *mergeinfo = NULL;
68 return SVN_NO_ERROR;
71 svn_error_t *
72 svn_client__record_wc_mergeinfo(const char *wcpath,
73 apr_hash_t *mergeinfo,
74 svn_wc_adm_access_t *adm_access,
75 apr_pool_t *pool)
77 svn_string_t *mergeinfo_str;
79 /* Convert the mergeinfo (if any) into text for storage as a
80 property value. */
81 if (mergeinfo)
83 /* The WC will contain mergeinfo. */
84 SVN_ERR(svn_mergeinfo__to_string(&mergeinfo_str, mergeinfo, pool));
86 else
88 mergeinfo_str = NULL;
91 /* Record the new mergeinfo in the WC. */
92 /* ### Later, we'll want behavior more analogous to
93 ### svn_client__get_prop_from_wc(). */
94 return svn_wc_prop_set2(SVN_PROP_MERGEINFO, mergeinfo_str, wcpath,
95 adm_access, TRUE /* skip checks */, pool);
98 /*-----------------------------------------------------------------------*/
100 /*** Retrieving mergeinfo. ***/
102 /* Adjust merge sources in MERGEINFO (which is assumed to be non-NULL). */
103 static APR_INLINE void
104 adjust_mergeinfo_source_paths(apr_hash_t *mergeinfo, const char *walk_path,
105 apr_hash_t *wc_mergeinfo, apr_pool_t *pool)
107 apr_hash_index_t *hi;
108 const void *merge_source;
109 void *rangelist;
110 const char *path;
112 for (hi = apr_hash_first(NULL, wc_mergeinfo); hi; hi = apr_hash_next(hi))
114 /* Copy inherited mergeinfo into our output hash, adjusting the
115 merge source as appropriate. */
116 apr_hash_this(hi, &merge_source, NULL, &rangelist);
117 path = svn_path_join((const char *) merge_source, walk_path,
118 apr_hash_pool_get(mergeinfo));
119 /* ### If pool has a different lifetime than mergeinfo->pool,
120 ### this use of "rangelist" will be a problem... */
121 apr_hash_set(mergeinfo, path, APR_HASH_KEY_STRING, rangelist);
126 svn_error_t *
127 svn_client__get_wc_mergeinfo(apr_hash_t **mergeinfo,
128 svn_boolean_t *inherited,
129 svn_boolean_t pristine,
130 svn_mergeinfo_inheritance_t inherit,
131 const svn_wc_entry_t *entry,
132 const char *wcpath,
133 const char *limit_path,
134 const char **walked_path,
135 svn_wc_adm_access_t *adm_access,
136 svn_client_ctx_t *ctx,
137 apr_pool_t *pool)
139 const char *walk_path = "";
140 apr_hash_t *wc_mergeinfo;
141 svn_boolean_t switched;
143 if (limit_path)
144 SVN_ERR(svn_path_get_absolute(&limit_path, limit_path, pool));
146 while (TRUE)
148 /* Don't look for explicit mergeinfo on WCPATH if we are only
149 interested in inherited mergeinfo. */
150 if (inherit == svn_mergeinfo_nearest_ancestor)
152 wc_mergeinfo = NULL;
153 inherit = svn_mergeinfo_inherited;
155 else
157 /* Look for mergeinfo on WCPATH. If there isn't any and we want
158 inherited mergeinfo, walk towards the root of the WC until we
159 encounter either (a) an unversioned directory, or (b) mergeinfo.
160 If we encounter (b), use that inherited mergeinfo as our
161 baseline. */
162 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, entry, wcpath,
163 pristine, adm_access, ctx,
164 pool));
166 /* If WCPATH is switched, don't look any higher for inherited
167 mergeinfo. */
168 SVN_ERR(svn_wc__path_switched(wcpath, &switched, entry, pool));
169 if (switched)
170 break;
173 /* Subsequent svn_wc_adm_access_t need to be opened with
174 an absolute path so we can walk up and out of the WC
175 if necessary. If we are using LIMIT_PATH it needs to
176 be absolute too. */
177 #if defined(WIN32) || defined(__CYGWIN__)
178 /* On Windows a path is also absolute when it starts with
179 'H:/' where 'H' is any upper or lower case letter. */
180 if (strlen(wcpath) == 0
181 || ((strlen(wcpath) > 0 && wcpath[0] != '/')
182 && !(strlen(wcpath) > 2
183 && wcpath[1] == ':'
184 && wcpath[2] == '/'
185 && ((wcpath[0] >= 'A' && wcpath[0] <= 'Z')
186 || (wcpath[0] >= 'a' && wcpath[0] <= 'z')))))
187 #else
188 if (!(strlen(wcpath) > 0 && wcpath[0] == '/'))
189 #endif /* WIN32 or Cygwin */
191 SVN_ERR(svn_path_get_absolute(&wcpath, wcpath, pool));
194 if (wc_mergeinfo == NULL &&
195 inherit != svn_mergeinfo_explicit &&
196 !svn_dirent_is_root(wcpath, strlen(wcpath)))
198 svn_error_t *err;
200 /* Don't look any higher than the limit path. */
201 if (limit_path && strcmp(limit_path, wcpath) == 0)
202 break;
204 /* No explicit mergeinfo on this path. Look higher up the
205 directory tree while keeping track of what we've walked. */
206 walk_path = svn_path_join(svn_path_basename(wcpath, pool),
207 walk_path, pool);
208 wcpath = svn_path_dirname(wcpath, pool);
210 err = svn_wc_adm_open3(&adm_access, NULL, wcpath,
211 FALSE, 0, NULL, NULL, pool);
212 if (err)
214 if (err->apr_err == SVN_ERR_WC_NOT_DIRECTORY)
216 svn_error_clear(err);
217 err = SVN_NO_ERROR;
218 *inherited = FALSE;
219 *mergeinfo = wc_mergeinfo;
221 return err;
224 SVN_ERR(svn_wc_entry(&entry, wcpath, adm_access, FALSE, pool));
226 if (entry)
227 /* We haven't yet risen above the root of the WC. */
228 continue;
230 break;
233 if (svn_path_is_empty(walk_path))
235 /* Mergeinfo is explicit. */
236 *inherited = FALSE;
237 *mergeinfo = wc_mergeinfo;
239 else
241 /* Mergeinfo may be inherited. */
242 if (wc_mergeinfo)
244 *inherited = (wc_mergeinfo != NULL);
245 *mergeinfo = apr_hash_make(pool);
246 adjust_mergeinfo_source_paths(*mergeinfo, walk_path, wc_mergeinfo,
247 pool);
249 else
251 *inherited = FALSE;
252 *mergeinfo = NULL;
256 if (walked_path)
257 *walked_path = walk_path;
259 /* Remove non-inheritable mergeinfo and paths mapped to empty ranges
260 which may occur if WCPATH's mergeinfo is not explicit. */
261 if (*inherited)
263 SVN_ERR(svn_mergeinfo_inheritable(mergeinfo, *mergeinfo, NULL,
264 SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, pool));
265 svn_mergeinfo__remove_empty_rangelists(*mergeinfo, pool);
267 return SVN_NO_ERROR;
271 svn_error_t *
272 svn_client__get_repos_mergeinfo(svn_ra_session_t *ra_session,
273 apr_hash_t **target_mergeinfo,
274 const char *rel_path,
275 svn_revnum_t rev,
276 svn_mergeinfo_inheritance_t inherit,
277 svn_boolean_t squelch_incapable,
278 apr_pool_t *pool)
280 svn_error_t *err;
281 apr_hash_t *repos_mergeinfo;
282 const char *old_session_url;
283 apr_array_header_t *rel_paths = apr_array_make(pool, 1, sizeof(rel_path));
285 APR_ARRAY_PUSH(rel_paths, const char *) = rel_path;
287 /* Temporarily point the session at the root of the repository. */
288 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
289 NULL, pool));
291 /* Fetch the mergeinfo. */
292 err = svn_ra_get_mergeinfo(ra_session, &repos_mergeinfo, rel_paths, rev,
293 inherit, pool);
294 if (err)
296 if (squelch_incapable && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
298 svn_error_clear(err);
299 repos_mergeinfo = NULL;
301 else
302 return err;
305 /* If we reparented the session, put it back where our caller had it. */
306 if (old_session_url)
307 SVN_ERR(svn_ra_reparent(ra_session, old_session_url, pool));
309 /* Grab only the mergeinfo provided for REL_PATH. */
310 if (repos_mergeinfo)
311 *target_mergeinfo = apr_hash_get(repos_mergeinfo, rel_path,
312 APR_HASH_KEY_STRING);
313 else
314 *target_mergeinfo = NULL;
316 return SVN_NO_ERROR;
320 svn_error_t *
321 svn_client__get_wc_or_repos_mergeinfo(apr_hash_t **target_mergeinfo,
322 const svn_wc_entry_t *entry,
323 svn_boolean_t *indirect,
324 svn_boolean_t repos_only,
325 svn_mergeinfo_inheritance_t inherit,
326 svn_ra_session_t *ra_session,
327 const char *target_wcpath,
328 svn_wc_adm_access_t *adm_access,
329 svn_client_ctx_t *ctx,
330 apr_pool_t *pool)
332 const char *url;
333 svn_revnum_t target_rev;
335 /* We may get an entry with abrieviated information from TARGET_WCPATH's
336 parent if TARGET_WCPATH is missing. These limited entries do not have
337 a URL and without that we cannot get accurate mergeinfo for
338 TARGET_WCPATH. */
339 SVN_ERR(svn_client__entry_location(&url, &target_rev, target_wcpath,
340 svn_opt_revision_working, entry, pool));
342 if (repos_only)
343 *target_mergeinfo = NULL;
344 else
345 SVN_ERR(svn_client__get_wc_mergeinfo(target_mergeinfo, indirect, FALSE,
346 inherit, entry, target_wcpath,
347 NULL, NULL, adm_access, ctx, pool));
349 /* If there in no WC mergeinfo check the repository. */
350 if (*target_mergeinfo == NULL)
352 apr_hash_t *repos_mergeinfo;
354 /* No need to check the repos is this is a local addition. */
355 if (entry->schedule != svn_wc_schedule_add)
357 apr_hash_t *props = apr_hash_make(pool);
359 /* Get the pristine SVN_PROP_MERGEINFO.
360 If it exists, then it should have been deleted by the local
361 merges. So don't get the mergeinfo from the repository. Just
362 assume the mergeinfo to be NULL.
364 SVN_ERR(svn_client__get_prop_from_wc(props, SVN_PROP_MERGEINFO,
365 target_wcpath, TRUE, entry,
366 adm_access, svn_depth_empty,
367 ctx, pool));
368 if (apr_hash_get(props, target_wcpath, APR_HASH_KEY_STRING) == NULL)
370 const char *repos_rel_path;
372 if (ra_session == NULL)
373 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url,
374 NULL, NULL, NULL,
375 FALSE, TRUE, ctx,
376 pool));
378 SVN_ERR(svn_client__path_relative_to_root(&repos_rel_path, url,
379 entry->repos, FALSE,
380 ra_session, NULL,
381 pool));
382 SVN_ERR(svn_client__get_repos_mergeinfo(ra_session,
383 &repos_mergeinfo,
384 repos_rel_path,
385 target_rev,
386 inherit,
387 TRUE,
388 pool));
389 if (repos_mergeinfo)
391 *target_mergeinfo = repos_mergeinfo;
392 *indirect = TRUE;
397 return SVN_NO_ERROR;
401 svn_error_t *
402 svn_client__get_history_as_mergeinfo(apr_hash_t **mergeinfo_p,
403 const char *path_or_url,
404 const svn_opt_revision_t *peg_revision,
405 svn_revnum_t range_youngest,
406 svn_revnum_t range_oldest,
407 svn_ra_session_t *ra_session,
408 svn_wc_adm_access_t *adm_access,
409 svn_client_ctx_t *ctx,
410 apr_pool_t *pool)
412 apr_array_header_t *segments;
413 svn_revnum_t peg_revnum = SVN_INVALID_REVNUM;
414 const char *url;
415 apr_hash_t *mergeinfo = apr_hash_make(pool);
416 apr_pool_t *sesspool = NULL; /* only used for an RA session we open */
417 svn_ra_session_t *session = ra_session;
418 int i;
420 /* If PATH_OR_URL is a local path (not a URL), we need to transform
421 it into a URL, open an RA session for it, and resolve the peg
422 revision. Note that if the local item is scheduled for addition
423 as a copy of something else, we'll use its copyfrom data to query
424 its history. */
425 SVN_ERR(svn_client__derive_location(&url, &peg_revnum, path_or_url,
426 peg_revision, session, adm_access,
427 ctx, pool));
429 if (session == NULL)
431 sesspool = svn_pool_create(pool);
432 SVN_ERR(svn_client__open_ra_session_internal(&session, url, NULL, NULL,
433 NULL, FALSE, TRUE, ctx,
434 sesspool));
437 /* Fetch the location segments for our URL@PEG_REVNUM. */
438 if (! SVN_IS_VALID_REVNUM(range_youngest))
439 range_youngest = peg_revnum;
440 if (! SVN_IS_VALID_REVNUM(range_oldest))
441 range_oldest = 0;
442 SVN_ERR(svn_client__repos_location_segments(&segments, session, "",
443 peg_revnum, range_youngest,
444 range_oldest, ctx, pool));
446 /* Translate location segments into merge sources and ranges. */
447 for (i = 0; i < segments->nelts; i++)
449 svn_location_segment_t *segment =
450 APR_ARRAY_IDX(segments, i, svn_location_segment_t *);
451 apr_array_header_t *path_ranges;
452 svn_merge_range_t *range;
453 const char *source_path;
455 /* No path segment? Skip it. */
456 if (! segment->path)
457 continue;
459 /* Prepend a leading slash to our path. */
460 source_path = apr_pstrcat(pool, "/", segment->path, NULL);
462 /* See if we already stored ranges for this path. If not, make
463 a new list. */
464 path_ranges = apr_hash_get(mergeinfo, source_path, APR_HASH_KEY_STRING);
465 if (! path_ranges)
466 path_ranges = apr_array_make(pool, 1, sizeof(range));
468 /* Build a merge range, push it onto the list of ranges, and for
469 good measure, (re)store it in the hash. */
470 range = apr_pcalloc(pool, sizeof(*range));
471 range->start = MAX(segment->range_start - 1, 0);
472 range->end = segment->range_end;
473 range->inheritable = TRUE;
474 APR_ARRAY_PUSH(path_ranges, svn_merge_range_t *) = range;
475 apr_hash_set(mergeinfo, source_path, APR_HASH_KEY_STRING, path_ranges);
478 /* If we opened an RA session, ensure its closure. */
479 if (sesspool)
480 svn_pool_destroy(sesspool);
482 *mergeinfo_p = mergeinfo;
483 return SVN_NO_ERROR;
487 /*-----------------------------------------------------------------------*/
489 /*** Eliding mergeinfo. ***/
491 /* Helper for svn_client__elide_mergeinfo() and svn_client__elide_children().
493 Given a working copy PATH, its mergeinfo hash CHILD_MERGEINFO, and the
494 mergeinfo of PATH's nearest ancestor PARENT_MERGEINFO, compare
495 CHILD_MERGEINFO to PARENT_MERGEINFO to see if the former elides to
496 the latter, following the elision rules described in
497 svn_client__elide_mergeinfo()'s docstring -- Note: This function
498 assumes that PARENT_MERGEINFO is definitive; i.e. if it is NULL then
499 the caller not only walked the entire WC looking for inherited mergeinfo,
500 but queried the repository if none was found in the WC. This is rather
501 important since this function elides empty mergeinfo (or mergeinfo
502 containing only paths mapped to empty ranges) if PARENT_MERGEINFO is NULL,
503 and we don't want to do that unless we are *certain* that the empty
504 mergeinfo on PATH isn't overriding anything.
506 If elision (full or partial) does occur, then update PATH's mergeinfo
507 appropriately. If CHILD_MERGEINFO is NULL, do nothing.
509 If PATH_SUFFIX and PARENT_MERGEINFO are not NULL append PATH_SUFFIX to each
510 path in PARENT_MERGEINFO before performing the comparison. */
511 static svn_error_t *
512 elide_mergeinfo(apr_hash_t *parent_mergeinfo,
513 apr_hash_t *child_mergeinfo,
514 const char *path,
515 const char *path_suffix,
516 svn_wc_adm_access_t *adm_access,
517 apr_pool_t *pool)
519 apr_pool_t *subpool = NULL;
520 svn_boolean_t elides;
522 /* Easy out: No child mergeinfo to elide. */
523 if (child_mergeinfo == NULL)
525 elides = FALSE;
527 else if (apr_hash_count(child_mergeinfo) == 0)
529 /* Empty mergeinfo elides to empty mergeinfo or to "nothing",
530 i.e. it isn't overriding any parent. Otherwise it doesn't
531 elide. */
532 if (!parent_mergeinfo || apr_hash_count(parent_mergeinfo) == 0)
533 elides = TRUE;
534 else
535 elides = FALSE;
537 else if (!parent_mergeinfo || apr_hash_count(parent_mergeinfo) == 0)
539 /* Non-empty mergeinfo never elides to empty mergeinfo
540 or no mergeinfo. */
541 elides = FALSE;
543 else
545 /* Both CHILD_MERGEINFO and PARENT_MERGEINFO are non-NULL and
546 non-empty. */
547 apr_hash_t *path_tweaked_parent_mergeinfo;
548 subpool = svn_pool_create(pool);
550 path_tweaked_parent_mergeinfo = apr_hash_make(subpool);
552 /* If we need to adjust the paths in PARENT_MERGEINFO do it now. */
553 if (path_suffix)
554 adjust_mergeinfo_source_paths(path_tweaked_parent_mergeinfo,
555 path_suffix, child_mergeinfo,
556 subpool);
557 else
558 path_tweaked_parent_mergeinfo = parent_mergeinfo;
560 SVN_ERR(svn_mergeinfo__equals(&elides,
561 path_tweaked_parent_mergeinfo,
562 child_mergeinfo, TRUE, subpool));
565 if (elides)
566 SVN_ERR(svn_wc_prop_set2(SVN_PROP_MERGEINFO, NULL, path, adm_access,
567 TRUE, pool));
569 if (subpool)
570 svn_pool_destroy(subpool);
572 return SVN_NO_ERROR;
576 svn_error_t *
577 svn_client__elide_children(apr_array_header_t *children_with_mergeinfo,
578 const char *target_wcpath,
579 const svn_wc_entry_t *entry,
580 svn_wc_adm_access_t *adm_access,
581 svn_client_ctx_t *ctx,
582 apr_pool_t *pool)
584 if (children_with_mergeinfo && children_with_mergeinfo->nelts)
586 int i;
587 const char *last_immediate_child;
588 apr_hash_t *target_mergeinfo;
589 apr_pool_t *iterpool = svn_pool_create(pool);
591 /* Get mergeinfo for the target of the merge. */
592 SVN_ERR(svn_client__parse_mergeinfo(&target_mergeinfo, entry,
593 target_wcpath, FALSE,
594 adm_access, ctx, pool));
596 /* For each immediate child of the merge target check if
597 its merginfo elides to the target. */
598 for (i = 0; i < children_with_mergeinfo->nelts; i++)
600 apr_hash_t *child_mergeinfo;
601 svn_boolean_t switched;
602 const svn_wc_entry_t *child_entry;
603 svn_client__merge_path_t *child =
604 APR_ARRAY_IDX(children_with_mergeinfo, i,
605 svn_client__merge_path_t *);
606 svn_pool_clear(iterpool);
608 if (!child)
609 continue;
611 if (child->absent)
612 continue;
614 if (i == 0)
616 /* children_with_mergeinfo is sorted depth
617 first so first path might be the target of
618 the merge if the target had mergeinfo prior
619 to the start of the merge. */
620 if (strcmp(target_wcpath, child->path) == 0)
622 last_immediate_child = NULL;
623 continue;
625 last_immediate_child = child->path;
627 else if (last_immediate_child
628 && svn_path_is_ancestor(last_immediate_child, child->path))
630 /* Not an immediate child. */
631 continue;
633 else
635 /* Found the first (last_immediate_child == NULL)
636 or another immediate child. */
637 last_immediate_child = child->path;
640 /* Don't try to elide switched children. */
641 SVN_ERR(svn_wc__entry_versioned(&child_entry, child->path,
642 adm_access, FALSE, iterpool));
643 SVN_ERR(svn_wc__path_switched(child->path, &switched, child_entry,
644 iterpool));
645 if (!switched)
647 const char *path_prefix = svn_path_dirname(child->path,
648 iterpool);
649 const char *path_suffix = svn_path_basename(child->path,
650 iterpool);
652 SVN_ERR(svn_client__parse_mergeinfo(&child_mergeinfo, entry,
653 child->path, FALSE,
654 adm_access, ctx, iterpool));
656 while (strcmp(path_prefix, target_wcpath) != 0)
658 path_suffix = svn_path_join(svn_path_basename(path_prefix,
659 iterpool),
660 path_suffix, iterpool);
661 path_prefix = svn_path_dirname(path_prefix, iterpool);
664 SVN_ERR(elide_mergeinfo(target_mergeinfo, child_mergeinfo,
665 child->path, path_suffix, adm_access,
666 iterpool));
669 svn_pool_destroy(iterpool);
672 return SVN_NO_ERROR;
676 svn_error_t *
677 svn_client__elide_mergeinfo(const char *target_wcpath,
678 const char *wc_elision_limit_path,
679 const svn_wc_entry_t *entry,
680 svn_wc_adm_access_t *adm_access,
681 svn_client_ctx_t *ctx,
682 apr_pool_t *pool)
684 /* Check for first easy out: We are already at the limit path. */
685 if (!wc_elision_limit_path
686 || strcmp(target_wcpath, wc_elision_limit_path) != 0)
688 apr_hash_t *target_mergeinfo;
689 apr_hash_t *mergeinfo = NULL;
690 svn_boolean_t inherited, switched;
691 const char *walk_path;
693 /* Check for second easy out: TARGET_WCPATH is switched. */
694 SVN_ERR(svn_wc__path_switched(target_wcpath, &switched, entry, pool));
695 if (!switched)
697 /* Get the TARGET_WCPATH's explicit mergeinfo. */
698 SVN_ERR(svn_client__get_wc_mergeinfo(&target_mergeinfo, &inherited,
699 FALSE, svn_mergeinfo_inherited,
700 entry, target_wcpath,
701 wc_elision_limit_path
702 ? wc_elision_limit_path
703 : NULL,
704 &walk_path, adm_access,
705 ctx, pool));
707 /* If TARGET_WCPATH has no explicit mergeinfo, there's nothing to
708 elide, we're done. */
709 if (inherited || target_mergeinfo == NULL)
710 return SVN_NO_ERROR;
712 /* Get TARGET_WCPATH's inherited mergeinfo from the WC. */
713 SVN_ERR(svn_client__get_wc_mergeinfo(&mergeinfo, &inherited, FALSE,
714 svn_mergeinfo_nearest_ancestor,
715 entry, target_wcpath,
716 wc_elision_limit_path
717 ? wc_elision_limit_path
718 : NULL,
719 &walk_path, adm_access,
720 ctx, pool));
722 /* If TARGET_WCPATH inherited no mergeinfo from the WC and we are
723 not limiting our search to the working copy then check if it
724 inherits any from the repos. */
725 if (!mergeinfo && !wc_elision_limit_path)
727 SVN_ERR(svn_client__get_wc_or_repos_mergeinfo
728 (&mergeinfo, entry, &inherited, TRUE,
729 svn_mergeinfo_nearest_ancestor,
730 NULL, target_wcpath, adm_access, ctx, pool));
733 /* If there is nowhere to elide TARGET_WCPATH's mergeinfo to and
734 the elision is limited, then we are done.*/
735 if (!mergeinfo && wc_elision_limit_path)
736 return SVN_NO_ERROR;
738 SVN_ERR(elide_mergeinfo(mergeinfo, target_mergeinfo, target_wcpath,
739 NULL, adm_access, pool));
742 return SVN_NO_ERROR;
745 svn_error_t *
746 svn_client__elide_mergeinfo_for_tree(apr_hash_t *children_with_mergeinfo,
747 svn_wc_adm_access_t *adm_access,
748 svn_client_ctx_t *ctx,
749 apr_pool_t *pool)
751 int i;
752 apr_pool_t *iterpool = svn_pool_create(pool);
753 apr_array_header_t *sorted_children =
754 svn_sort__hash(children_with_mergeinfo, svn_sort_compare_items_as_paths,
755 pool);
757 /* sorted_children is in depth first order. To minimize
758 svn_client__elide_mergeinfo()'s crawls up the working copy from
759 each child, run through the array backwards, effectively doing a
760 right-left post-order traversal. */
761 for (i = sorted_children->nelts -1; i >= 0; i--)
763 const svn_wc_entry_t *child_entry;
764 const char *child_wcpath;
765 svn_sort__item_t *item = &APR_ARRAY_IDX(sorted_children, i,
766 svn_sort__item_t);
767 svn_pool_clear(iterpool);
768 child_wcpath = item->key;
769 SVN_ERR(svn_wc__entry_versioned(&child_entry, child_wcpath, adm_access,
770 FALSE, iterpool));
771 SVN_ERR(svn_client__elide_mergeinfo(child_wcpath, NULL, child_entry,
772 adm_access, ctx, iterpool));
775 svn_pool_destroy(iterpool);
776 return SVN_NO_ERROR;
780 /* If the server supports Merge Tracking, set *MERGEINFO to a hash
781 mapping const char * root-relative source paths to an
782 apr_array_header_t * list of svn_merge_range_t * revision ranges
783 representing merge sources and corresponding revision ranges which
784 have been merged into PATH_OR_URL as of PEG_REVISION, or NULL if
785 there is no mergeinfo. Set *REPOS_ROOT to the root URL of the
786 repository associated with PATH_OR_URL (and to which the paths in
787 *MERGEINFO are relative). If the server does not support Merge Tracking,
788 return an error with the code SVN_ERR_UNSUPPORTED_FEATURE. Use
789 POOL for allocation of all returned values. */
790 static svn_error_t *
791 get_mergeinfo(apr_hash_t **mergeinfo,
792 const char **repos_root,
793 const char *path_or_url,
794 const svn_opt_revision_t *peg_revision,
795 svn_client_ctx_t *ctx,
796 apr_pool_t *pool)
798 apr_pool_t *subpool = svn_pool_create(pool);
799 svn_ra_session_t *ra_session;
800 svn_revnum_t rev;
802 if (svn_path_is_url(path_or_url))
804 const char *repos_rel_path;
806 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, path_or_url,
807 NULL, NULL, NULL, FALSE,
808 TRUE, ctx, subpool));
809 SVN_ERR(svn_client__get_revision_number(&rev, NULL, ra_session,
810 peg_revision, "", subpool));
811 SVN_ERR(svn_ra_get_repos_root(ra_session, repos_root, pool));
812 SVN_ERR(svn_client__path_relative_to_root(&repos_rel_path, path_or_url,
813 *repos_root, FALSE, NULL,
814 NULL, subpool));
815 SVN_ERR(svn_client__get_repos_mergeinfo(ra_session, mergeinfo,
816 repos_rel_path, rev,
817 svn_mergeinfo_inherited, FALSE,
818 pool));
820 else /* ! svn_path_is_url() */
822 svn_wc_adm_access_t *adm_access;
823 const svn_wc_entry_t *entry;
824 const char *url;
825 svn_boolean_t indirect;
827 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, path_or_url, FALSE,
828 0, ctx->cancel_func, ctx->cancel_baton,
829 subpool));
830 SVN_ERR(svn_wc__entry_versioned(&entry, path_or_url, adm_access, FALSE,
831 subpool));
833 /* Check server Merge Tracking capability. */
834 SVN_ERR(svn_client__entry_location(&url, &rev, path_or_url,
835 svn_opt_revision_working, entry,
836 subpool));
837 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url,
838 NULL, NULL, NULL, FALSE,
839 TRUE, ctx, subpool));
840 SVN_ERR(svn_ra__assert_mergeinfo_capable_server(ra_session, path_or_url,
841 subpool));
843 /* Acquire return values. */
844 SVN_ERR(svn_client__get_repos_root(repos_root, path_or_url, peg_revision,
845 adm_access, ctx, pool));
846 SVN_ERR(svn_client__get_wc_or_repos_mergeinfo(mergeinfo, entry,
847 &indirect, FALSE,
848 svn_mergeinfo_inherited,
849 NULL, path_or_url,
850 adm_access, ctx, pool));
851 SVN_ERR(svn_wc_adm_close(adm_access));
854 svn_pool_destroy(subpool);
855 return SVN_NO_ERROR;
859 /*** Public APIs ***/
861 svn_error_t *
862 svn_client_mergeinfo_get_merged(apr_hash_t **mergeinfo,
863 const char *path_or_url,
864 const svn_opt_revision_t *peg_revision,
865 svn_client_ctx_t *ctx,
866 apr_pool_t *pool)
868 const char *repos_root;
869 apr_hash_t *full_path_mergeinfo;
871 SVN_ERR(get_mergeinfo(mergeinfo, &repos_root, path_or_url,
872 peg_revision, ctx, pool));
874 /* Copy the MERGEINFO hash items into another hash, but change
875 the relative paths into full URLs. */
876 if (*mergeinfo)
878 apr_hash_index_t *hi;
880 full_path_mergeinfo = apr_hash_make(pool);
881 for (hi = apr_hash_first(pool, *mergeinfo); hi; hi = apr_hash_next(hi))
883 const void *key;
884 void *val;
885 const char *source_url;
887 apr_hash_this(hi, &key, NULL, &val);
888 source_url = svn_path_uri_encode(key, pool);
889 source_url = svn_path_join(repos_root, source_url + 1, pool);
890 apr_hash_set(full_path_mergeinfo, source_url,
891 APR_HASH_KEY_STRING, val);
893 *mergeinfo = full_path_mergeinfo;
896 return SVN_NO_ERROR;
900 svn_error_t *
901 svn_client_mergeinfo_get_available(apr_array_header_t **rangelist,
902 const char *path_or_url,
903 const svn_opt_revision_t *peg_revision,
904 const char *merge_source_url,
905 svn_client_ctx_t *ctx,
906 apr_pool_t *pool)
908 apr_hash_t *mergeinfo, *history, *source_history, *available;
909 apr_hash_index_t *hi;
910 svn_ra_session_t *ra_session;
911 int num_ranges = 0;
912 const char *repos_root;
913 apr_pool_t *sesspool = svn_pool_create(pool);
914 svn_opt_revision_t head_revision;
915 head_revision.kind = svn_opt_revision_head;
917 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, merge_source_url,
918 NULL, NULL, NULL, FALSE,
919 TRUE, ctx, sesspool));
921 /* Step 1: Across the set of possible merges, see what's already
922 been merged into PATH_OR_URL@PEG_REVISION (or what's already part
923 of the history it shares with that of MERGE_SOURCE_URL. */
924 SVN_ERR(get_mergeinfo(&mergeinfo, &repos_root, path_or_url,
925 peg_revision, ctx, pool));
926 SVN_ERR(svn_client__get_history_as_mergeinfo(&history,
927 path_or_url,
928 peg_revision,
929 SVN_INVALID_REVNUM,
930 SVN_INVALID_REVNUM,
931 NULL, NULL, ctx, pool));
932 if (! mergeinfo)
933 mergeinfo = history;
934 else
935 svn_mergeinfo_merge(mergeinfo, history, pool);
937 /* Step 2: See what merge sources can be derived from the history of
938 MERGE_SOURCE_URL. */
939 SVN_ERR(svn_client__get_history_as_mergeinfo(&source_history,
940 merge_source_url,
941 &head_revision,
942 SVN_INVALID_REVNUM,
943 SVN_INVALID_REVNUM,
944 ra_session, NULL, ctx, pool));
945 svn_pool_destroy(sesspool);
947 /* Now, we want to remove from the possible mergeinfo
948 (SOURCE_HISTORY) the merges already present in our PATH_OR_URL. */
949 SVN_ERR(svn_mergeinfo_remove(&available, mergeinfo, source_history, pool));
951 /* Finally, we want to provide a simple, single revision range list
952 to our caller. Now, interestingly, if MERGE_SOURCE_URL has been
953 renamed over time, there's good chance that set of available
954 merges have different paths assigned to them. Fortunately, we
955 know that we can't have any two paths in AVAILABLE with
956 overlapping revisions (because the original SOURCE_HISTORY also
957 had this property). So we'll just collapse into one rangelist
958 all the rangelists across all the paths in AVAILABLE. */
959 *rangelist = apr_array_make(pool, num_ranges, sizeof(svn_merge_range_t *));
960 for (hi = apr_hash_first(pool, available); hi; hi = apr_hash_next(hi))
962 void *val;
963 apr_hash_this(hi, NULL, NULL, &val);
964 SVN_ERR(svn_rangelist_merge(rangelist, val, pool));
966 return SVN_NO_ERROR;
970 svn_error_t *
971 svn_client_suggest_merge_sources(apr_array_header_t **suggestions,
972 const char *path_or_url,
973 const svn_opt_revision_t *peg_revision,
974 svn_client_ctx_t *ctx,
975 apr_pool_t *pool)
977 const char *repos_root;
978 const char *copyfrom_path;
979 apr_array_header_t *list;
980 svn_revnum_t copyfrom_rev;
981 apr_hash_t *mergeinfo;
982 apr_hash_index_t *hi;
984 list = apr_array_make(pool, 1, sizeof(const char *));
986 /* In our ideal algorithm, the list of recommendations should be
987 ordered by:
989 1. The most recent existing merge source.
990 2. The copyfrom source (which will also be listed as a merge
991 source if the copy was made with a 1.5+ client and server).
992 3. All other merge sources, most recent to least recent.
994 However, determining the order of application of merge sources
995 requires a new RA API. Until such an API is available, our
996 algorithm will be:
998 1. The copyfrom source.
999 2. All remaining merge sources (unordered).
1002 /* ### TODO: Share ra_session batons to improve efficiency? */
1003 SVN_ERR(svn_client__get_repos_root(&repos_root, path_or_url, peg_revision,
1004 NULL, ctx, pool));
1005 SVN_ERR(svn_client__get_copy_source(path_or_url, peg_revision,
1006 &copyfrom_path, &copyfrom_rev,
1007 ctx, pool));
1008 if (copyfrom_path)
1010 copyfrom_path = svn_path_join(repos_root,
1011 svn_path_uri_encode(copyfrom_path + 1,
1012 pool),
1013 pool);
1014 APR_ARRAY_PUSH(list, const char *) = copyfrom_path;
1017 SVN_ERR(svn_client_mergeinfo_get_merged(&mergeinfo, path_or_url,
1018 peg_revision, ctx, pool));
1019 if (mergeinfo)
1021 for (hi = apr_hash_first(NULL, mergeinfo); hi; hi = apr_hash_next(hi))
1023 const char *merge_path;
1024 apr_hash_this(hi, (void *)(&merge_path), NULL, NULL);
1025 if (copyfrom_path == NULL || strcmp(merge_path, copyfrom_path) != 0)
1026 APR_ARRAY_PUSH(list, const char *) = merge_path;
1030 *suggestions = list;
1031 return SVN_NO_ERROR;