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>
23 #include "svn_pools.h"
25 #include "svn_string.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"
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"
39 #include "mergeinfo.h"
40 #include "svn_private_config.h"
45 svn_client__parse_mergeinfo(apr_hash_t
**mergeinfo
,
46 const svn_wc_entry_t
*entry
,
48 svn_boolean_t pristine
,
49 svn_wc_adm_access_t
*adm_access
,
50 svn_client_ctx_t
*ctx
,
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
);
64 SVN_ERR(svn_mergeinfo_parse(mergeinfo
, propval
->data
, pool
));
72 svn_client__record_wc_mergeinfo(const char *wcpath
,
73 apr_hash_t
*mergeinfo
,
74 svn_wc_adm_access_t
*adm_access
,
77 svn_string_t
*mergeinfo_str
;
79 /* Convert the mergeinfo (if any) into text for storage as a
83 /* The WC will contain mergeinfo. */
84 SVN_ERR(svn_mergeinfo__to_string(&mergeinfo_str
, mergeinfo
, pool
));
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
;
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
);
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
,
133 const char *limit_path
,
134 const char **walked_path
,
135 svn_wc_adm_access_t
*adm_access
,
136 svn_client_ctx_t
*ctx
,
139 const char *walk_path
= "";
140 apr_hash_t
*wc_mergeinfo
;
141 svn_boolean_t switched
;
144 SVN_ERR(svn_path_get_absolute(&limit_path
, limit_path
, pool
));
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
)
153 inherit
= svn_mergeinfo_inherited
;
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
162 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo
, entry
, wcpath
,
163 pristine
, adm_access
, ctx
,
166 /* If WCPATH is switched, don't look any higher for inherited
168 SVN_ERR(svn_wc__path_switched(wcpath
, &switched
, entry
, pool
));
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
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
185 && ((wcpath
[0] >= 'A' && wcpath
[0] <= 'Z')
186 || (wcpath
[0] >= 'a' && wcpath
[0] <= 'z')))))
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
)))
200 /* Don't look any higher than the limit path. */
201 if (limit_path
&& strcmp(limit_path
, wcpath
) == 0)
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
),
208 wcpath
= svn_path_dirname(wcpath
, pool
);
210 err
= svn_wc_adm_open3(&adm_access
, NULL
, wcpath
,
211 FALSE
, 0, NULL
, NULL
, pool
);
214 if (err
->apr_err
== SVN_ERR_WC_NOT_DIRECTORY
)
216 svn_error_clear(err
);
219 *mergeinfo
= wc_mergeinfo
;
224 SVN_ERR(svn_wc_entry(&entry
, wcpath
, adm_access
, FALSE
, pool
));
227 /* We haven't yet risen above the root of the WC. */
233 if (svn_path_is_empty(walk_path
))
235 /* Mergeinfo is explicit. */
237 *mergeinfo
= wc_mergeinfo
;
241 /* Mergeinfo may be inherited. */
244 *inherited
= (wc_mergeinfo
!= NULL
);
245 *mergeinfo
= apr_hash_make(pool
);
246 adjust_mergeinfo_source_paths(*mergeinfo
, walk_path
, wc_mergeinfo
,
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. */
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
);
272 svn_client__get_repos_mergeinfo(svn_ra_session_t
*ra_session
,
273 apr_hash_t
**target_mergeinfo
,
274 const char *rel_path
,
276 svn_mergeinfo_inheritance_t inherit
,
277 svn_boolean_t squelch_incapable
,
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
,
291 /* Fetch the mergeinfo. */
292 err
= svn_ra_get_mergeinfo(ra_session
, &repos_mergeinfo
, rel_paths
, rev
,
296 if (squelch_incapable
&& err
->apr_err
== SVN_ERR_UNSUPPORTED_FEATURE
)
298 svn_error_clear(err
);
299 repos_mergeinfo
= NULL
;
305 /* If we reparented the session, put it back where our caller had it. */
307 SVN_ERR(svn_ra_reparent(ra_session
, old_session_url
, pool
));
309 /* Grab only the mergeinfo provided for REL_PATH. */
311 *target_mergeinfo
= apr_hash_get(repos_mergeinfo
, rel_path
,
312 APR_HASH_KEY_STRING
);
314 *target_mergeinfo
= NULL
;
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
,
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
339 SVN_ERR(svn_client__entry_location(&url
, &target_rev
, target_wcpath
,
340 svn_opt_revision_working
, entry
, pool
));
343 *target_mergeinfo
= NULL
;
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
,
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
,
378 SVN_ERR(svn_client__path_relative_to_root(&repos_rel_path
, url
,
382 SVN_ERR(svn_client__get_repos_mergeinfo(ra_session
,
391 *target_mergeinfo
= repos_mergeinfo
;
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
,
412 apr_array_header_t
*segments
;
413 svn_revnum_t peg_revnum
= SVN_INVALID_REVNUM
;
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
;
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
425 SVN_ERR(svn_client__derive_location(&url
, &peg_revnum
, path_or_url
,
426 peg_revision
, session
, adm_access
,
431 sesspool
= svn_pool_create(pool
);
432 SVN_ERR(svn_client__open_ra_session_internal(&session
, url
, NULL
, NULL
,
433 NULL
, FALSE
, TRUE
, ctx
,
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
))
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. */
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
464 path_ranges
= apr_hash_get(mergeinfo
, source_path
, APR_HASH_KEY_STRING
);
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. */
480 svn_pool_destroy(sesspool
);
482 *mergeinfo_p
= mergeinfo
;
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. */
512 elide_mergeinfo(apr_hash_t
*parent_mergeinfo
,
513 apr_hash_t
*child_mergeinfo
,
515 const char *path_suffix
,
516 svn_wc_adm_access_t
*adm_access
,
519 apr_pool_t
*subpool
= NULL
;
520 svn_boolean_t elides
;
522 /* Easy out: No child mergeinfo to elide. */
523 if (child_mergeinfo
== NULL
)
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
532 if (!parent_mergeinfo
|| apr_hash_count(parent_mergeinfo
) == 0)
537 else if (!parent_mergeinfo
|| apr_hash_count(parent_mergeinfo
) == 0)
539 /* Non-empty mergeinfo never elides to empty mergeinfo
545 /* Both CHILD_MERGEINFO and PARENT_MERGEINFO are non-NULL and
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. */
554 adjust_mergeinfo_source_paths(path_tweaked_parent_mergeinfo
,
555 path_suffix
, child_mergeinfo
,
558 path_tweaked_parent_mergeinfo
= parent_mergeinfo
;
560 SVN_ERR(svn_mergeinfo__equals(&elides
,
561 path_tweaked_parent_mergeinfo
,
562 child_mergeinfo
, TRUE
, subpool
));
566 SVN_ERR(svn_wc_prop_set2(SVN_PROP_MERGEINFO
, NULL
, path
, adm_access
,
570 svn_pool_destroy(subpool
);
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
,
584 if (children_with_mergeinfo
&& children_with_mergeinfo
->nelts
)
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
);
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
;
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. */
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
,
647 const char *path_prefix
= svn_path_dirname(child
->path
,
649 const char *path_suffix
= svn_path_basename(child
->path
,
652 SVN_ERR(svn_client__parse_mergeinfo(&child_mergeinfo
, entry
,
654 adm_access
, ctx
, iterpool
));
656 while (strcmp(path_prefix
, target_wcpath
) != 0)
658 path_suffix
= svn_path_join(svn_path_basename(path_prefix
,
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
,
669 svn_pool_destroy(iterpool
);
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
,
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
));
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
704 &walk_path
, adm_access
,
707 /* If TARGET_WCPATH has no explicit mergeinfo, there's nothing to
708 elide, we're done. */
709 if (inherited
|| target_mergeinfo
== NULL
)
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
719 &walk_path
, adm_access
,
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
)
738 SVN_ERR(elide_mergeinfo(mergeinfo
, target_mergeinfo
, target_wcpath
,
739 NULL
, adm_access
, pool
));
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
,
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
,
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
,
767 svn_pool_clear(iterpool
);
768 child_wcpath
= item
->key
;
769 SVN_ERR(svn_wc__entry_versioned(&child_entry
, child_wcpath
, adm_access
,
771 SVN_ERR(svn_client__elide_mergeinfo(child_wcpath
, NULL
, child_entry
,
772 adm_access
, ctx
, iterpool
));
775 svn_pool_destroy(iterpool
);
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. */
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
,
798 apr_pool_t
*subpool
= svn_pool_create(pool
);
799 svn_ra_session_t
*ra_session
;
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
,
815 SVN_ERR(svn_client__get_repos_mergeinfo(ra_session
, mergeinfo
,
817 svn_mergeinfo_inherited
, FALSE
,
820 else /* ! svn_path_is_url() */
822 svn_wc_adm_access_t
*adm_access
;
823 const svn_wc_entry_t
*entry
;
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
,
830 SVN_ERR(svn_wc__entry_versioned(&entry
, path_or_url
, adm_access
, FALSE
,
833 /* Check server Merge Tracking capability. */
834 SVN_ERR(svn_client__entry_location(&url
, &rev
, path_or_url
,
835 svn_opt_revision_working
, entry
,
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
,
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
,
848 svn_mergeinfo_inherited
,
850 adm_access
, ctx
, pool
));
851 SVN_ERR(svn_wc_adm_close(adm_access
));
854 svn_pool_destroy(subpool
);
859 /*** Public APIs ***/
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
,
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. */
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
))
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
;
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
,
908 apr_hash_t
*mergeinfo
, *history
, *source_history
, *available
;
909 apr_hash_index_t
*hi
;
910 svn_ra_session_t
*ra_session
;
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
,
931 NULL
, NULL
, ctx
, pool
));
935 svn_mergeinfo_merge(mergeinfo
, history
, pool
);
937 /* Step 2: See what merge sources can be derived from the history of
939 SVN_ERR(svn_client__get_history_as_mergeinfo(&source_history
,
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
))
963 apr_hash_this(hi
, NULL
, NULL
, &val
);
964 SVN_ERR(svn_rangelist_merge(rangelist
, val
, pool
));
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
,
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
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
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
,
1005 SVN_ERR(svn_client__get_copy_source(path_or_url
, peg_revision
,
1006 ©from_path
, ©from_rev
,
1010 copyfrom_path
= svn_path_join(repos_root
,
1011 svn_path_uri_encode(copyfrom_path
+ 1,
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
));
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
;