Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_client / mergeinfo.c
blob21a9d06f974b65c34f64df59d6883a8c83a483cd
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"
34 #include "svn_hash.h"
36 #include "private/svn_mergeinfo_private.h"
37 #include "private/svn_wc_private.h"
38 #include "private/svn_ra_private.h"
39 #include "client.h"
40 #include "mergeinfo.h"
41 #include "svn_private_config.h"
45 svn_error_t *
46 svn_client__parse_mergeinfo(svn_mergeinfo_t *mergeinfo,
47 const svn_wc_entry_t *entry,
48 const char *wcpath,
49 svn_boolean_t pristine,
50 svn_wc_adm_access_t *adm_access,
51 svn_client_ctx_t *ctx,
52 apr_pool_t *pool)
54 apr_hash_t *props = apr_hash_make(pool);
55 const svn_string_t *propval;
57 /* ### Use svn_wc_prop_get() would actually be sufficient for now.
58 ### DannyB thinks that later we'll need behavior more like
59 ### svn_client__get_prop_from_wc(). */
60 SVN_ERR(svn_client__get_prop_from_wc(props, SVN_PROP_MERGEINFO,
61 wcpath, pristine, entry, adm_access,
62 svn_depth_empty, NULL, ctx, pool));
63 propval = apr_hash_get(props, wcpath, APR_HASH_KEY_STRING);
64 if (propval)
65 SVN_ERR(svn_mergeinfo_parse(mergeinfo, propval->data, pool));
66 else
67 *mergeinfo = NULL;
69 return SVN_NO_ERROR;
72 svn_error_t *
73 svn_client__record_wc_mergeinfo(const char *wcpath,
74 svn_mergeinfo_t mergeinfo,
75 svn_wc_adm_access_t *adm_access,
76 apr_pool_t *pool)
78 svn_string_t *mergeinfo_str;
80 /* Convert the mergeinfo (if any) into text for storage as a
81 property value. */
82 if (mergeinfo)
84 /* The WC will contain mergeinfo. */
85 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_str, mergeinfo, pool));
87 else
89 mergeinfo_str = NULL;
92 /* Record the new mergeinfo in the WC. */
93 /* ### Later, we'll want behavior more analogous to
94 ### svn_client__get_prop_from_wc(). */
95 return svn_wc_prop_set2(SVN_PROP_MERGEINFO, mergeinfo_str, wcpath,
96 adm_access, TRUE /* skip checks */, pool);
99 /*-----------------------------------------------------------------------*/
101 /*** Retrieving mergeinfo. ***/
103 /* Adjust merge sources in MERGEINFO (which is assumed to be non-NULL). */
104 static APR_INLINE void
105 adjust_mergeinfo_source_paths(svn_mergeinfo_t mergeinfo, const char *walk_path,
106 svn_mergeinfo_t wc_mergeinfo, apr_pool_t *pool)
108 apr_hash_index_t *hi;
109 const void *merge_source;
110 void *rangelist;
111 const char *path;
113 for (hi = apr_hash_first(NULL, wc_mergeinfo); hi; hi = apr_hash_next(hi))
115 /* Copy inherited mergeinfo into our output hash, adjusting the
116 merge source as appropriate. */
117 apr_hash_this(hi, &merge_source, NULL, &rangelist);
118 path = svn_path_join((const char *) merge_source, walk_path,
119 apr_hash_pool_get(mergeinfo));
120 /* ### If pool has a different lifetime than mergeinfo->pool,
121 ### this use of "rangelist" will be a problem... */
122 apr_hash_set(mergeinfo, path, APR_HASH_KEY_STRING, rangelist);
127 svn_error_t *
128 svn_client__get_wc_mergeinfo(svn_mergeinfo_t *mergeinfo,
129 svn_boolean_t *inherited,
130 svn_boolean_t pristine,
131 svn_mergeinfo_inheritance_t inherit,
132 const svn_wc_entry_t *entry,
133 const char *wcpath,
134 const char *limit_path,
135 const char **walked_path,
136 svn_wc_adm_access_t *adm_access,
137 svn_client_ctx_t *ctx,
138 apr_pool_t *pool)
140 const char *walk_path = "";
141 svn_mergeinfo_t wc_mergeinfo;
142 svn_boolean_t switched;
143 svn_revnum_t base_revision = entry->revision;
145 if (limit_path)
146 SVN_ERR(svn_path_get_absolute(&limit_path, limit_path, pool));
148 while (TRUE)
150 /* Don't look for explicit mergeinfo on WCPATH if we are only
151 interested in inherited mergeinfo. */
152 if (inherit == svn_mergeinfo_nearest_ancestor)
154 wc_mergeinfo = NULL;
155 inherit = svn_mergeinfo_inherited;
157 else
159 /* Look for mergeinfo on WCPATH. If there isn't any and we want
160 inherited mergeinfo, walk towards the root of the WC until we
161 encounter either (a) an unversioned directory, or (b) mergeinfo.
162 If we encounter (b), use that inherited mergeinfo as our
163 baseline. */
164 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, entry, wcpath,
165 pristine, adm_access, ctx,
166 pool));
168 /* If WCPATH is switched, don't look any higher for inherited
169 mergeinfo. */
170 SVN_ERR(svn_wc__path_switched(wcpath, &switched, entry, pool));
171 if (switched)
172 break;
175 /* Subsequent svn_wc_adm_access_t need to be opened with
176 an absolute path so we can walk up and out of the WC
177 if necessary. If we are using LIMIT_PATH it needs to
178 be absolute too. */
179 #if defined(WIN32) || defined(__CYGWIN__)
180 /* On Windows a path is also absolute when it starts with
181 'H:/' where 'H' is any upper or lower case letter. */
182 if (strlen(wcpath) == 0
183 || ((strlen(wcpath) > 0 && wcpath[0] != '/')
184 && !(strlen(wcpath) > 2
185 && wcpath[1] == ':'
186 && wcpath[2] == '/'
187 && ((wcpath[0] >= 'A' && wcpath[0] <= 'Z')
188 || (wcpath[0] >= 'a' && wcpath[0] <= 'z')))))
189 #else
190 if (!(strlen(wcpath) > 0 && wcpath[0] == '/'))
191 #endif /* WIN32 or Cygwin */
193 SVN_ERR(svn_path_get_absolute(&wcpath, wcpath, pool));
196 if (wc_mergeinfo == NULL &&
197 inherit != svn_mergeinfo_explicit &&
198 !svn_dirent_is_root(wcpath, strlen(wcpath)))
200 svn_error_t *err;
202 /* Don't look any higher than the limit path. */
203 if (limit_path && strcmp(limit_path, wcpath) == 0)
204 break;
206 /* No explicit mergeinfo on this path. Look higher up the
207 directory tree while keeping track of what we've walked. */
208 walk_path = svn_path_join(svn_path_basename(wcpath, pool),
209 walk_path, pool);
210 wcpath = svn_path_dirname(wcpath, pool);
212 err = svn_wc_adm_open3(&adm_access, NULL, wcpath,
213 FALSE, 0, NULL, NULL, pool);
214 if (err)
216 if (err->apr_err == SVN_ERR_WC_NOT_DIRECTORY)
218 svn_error_clear(err);
219 err = SVN_NO_ERROR;
220 *inherited = FALSE;
221 *mergeinfo = wc_mergeinfo;
223 return err;
226 SVN_ERR(svn_wc_entry(&entry, wcpath, adm_access, FALSE, pool));
228 /* Look in WCPATH's parents only if the parents share the same
229 working revision. */
230 if (base_revision < entry->cmt_rev
231 || entry->revision < base_revision)
232 break;
234 if (entry)
235 /* We haven't yet risen above the root of the WC. */
236 continue;
238 break;
241 if (svn_path_is_empty(walk_path))
243 /* Mergeinfo is explicit. */
244 *inherited = FALSE;
245 *mergeinfo = wc_mergeinfo;
247 else
249 /* Mergeinfo may be inherited. */
250 if (wc_mergeinfo)
252 *inherited = (wc_mergeinfo != NULL);
253 *mergeinfo = apr_hash_make(pool);
254 adjust_mergeinfo_source_paths(*mergeinfo, walk_path, wc_mergeinfo,
255 pool);
257 else
259 *inherited = FALSE;
260 *mergeinfo = NULL;
264 if (walked_path)
265 *walked_path = walk_path;
267 /* Remove non-inheritable mergeinfo and paths mapped to empty ranges
268 which may occur if WCPATH's mergeinfo is not explicit. */
269 if (*inherited)
271 SVN_ERR(svn_mergeinfo_inheritable(mergeinfo, *mergeinfo, NULL,
272 SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, pool));
273 svn_mergeinfo__remove_empty_rangelists(*mergeinfo, pool);
275 return SVN_NO_ERROR;
279 svn_error_t *
280 svn_client__get_repos_mergeinfo(svn_ra_session_t *ra_session,
281 svn_mergeinfo_t *target_mergeinfo,
282 const char *rel_path,
283 svn_revnum_t rev,
284 svn_mergeinfo_inheritance_t inherit,
285 svn_boolean_t squelch_incapable,
286 apr_pool_t *pool)
288 svn_error_t *err;
289 svn_mergeinfo_t repos_mergeinfo;
290 const char *old_session_url;
291 apr_array_header_t *rel_paths = apr_array_make(pool, 1, sizeof(rel_path));
293 APR_ARRAY_PUSH(rel_paths, const char *) = rel_path;
295 /* Temporarily point the session at the root of the repository. */
296 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
297 NULL, pool));
299 /* Fetch the mergeinfo. */
300 err = svn_ra_get_mergeinfo(ra_session, &repos_mergeinfo, rel_paths, rev,
301 inherit, FALSE, pool);
302 if (err)
304 if (squelch_incapable && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
306 svn_error_clear(err);
307 repos_mergeinfo = NULL;
309 else
310 return err;
313 /* If we reparented the session, put it back where our caller had it. */
314 if (old_session_url)
315 SVN_ERR(svn_ra_reparent(ra_session, old_session_url, pool));
317 /* Grab only the mergeinfo provided for REL_PATH. */
318 if (repos_mergeinfo)
319 *target_mergeinfo = apr_hash_get(repos_mergeinfo, rel_path,
320 APR_HASH_KEY_STRING);
321 else
322 *target_mergeinfo = NULL;
324 return SVN_NO_ERROR;
328 svn_error_t *
329 svn_client__get_wc_or_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo,
330 const svn_wc_entry_t *entry,
331 svn_boolean_t *indirect,
332 svn_boolean_t repos_only,
333 svn_mergeinfo_inheritance_t inherit,
334 svn_ra_session_t *ra_session,
335 const char *target_wcpath,
336 svn_wc_adm_access_t *adm_access,
337 svn_client_ctx_t *ctx,
338 apr_pool_t *pool)
340 const char *url;
341 svn_revnum_t target_rev;
343 /* We may get an entry with abbreviated information from TARGET_WCPATH's
344 parent if TARGET_WCPATH is missing. These limited entries do not have
345 a URL and without that we cannot get accurate mergeinfo for
346 TARGET_WCPATH. */
347 SVN_ERR(svn_client__entry_location(&url, &target_rev, target_wcpath,
348 svn_opt_revision_working, entry, pool));
350 if (repos_only)
351 *target_mergeinfo = NULL;
352 else
353 SVN_ERR(svn_client__get_wc_mergeinfo(target_mergeinfo, indirect, FALSE,
354 inherit, entry, target_wcpath,
355 NULL, NULL, adm_access, ctx, pool));
357 /* If there is no WC mergeinfo check the repository. */
358 if (*target_mergeinfo == NULL)
360 svn_mergeinfo_t repos_mergeinfo;
362 /* No need to check the repos if this is a local addition. */
363 if (entry->schedule != svn_wc_schedule_add)
365 apr_hash_t *props = apr_hash_make(pool);
367 /* Get the pristine SVN_PROP_MERGEINFO.
368 If it exists, then it should have been deleted by the local
369 merges. So don't get the mergeinfo from the repository. Just
370 assume the mergeinfo to be NULL.
372 SVN_ERR(svn_client__get_prop_from_wc(props, SVN_PROP_MERGEINFO,
373 target_wcpath, TRUE, entry,
374 adm_access, svn_depth_empty,
375 NULL, ctx, pool));
376 if (apr_hash_get(props, target_wcpath, APR_HASH_KEY_STRING) == NULL)
378 const char *repos_rel_path;
380 if (ra_session == NULL)
381 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url,
382 NULL, NULL, NULL,
383 FALSE, TRUE, ctx,
384 pool));
386 SVN_ERR(svn_client__path_relative_to_root(&repos_rel_path, url,
387 entry->repos, FALSE,
388 ra_session, NULL,
389 pool));
390 SVN_ERR(svn_client__get_repos_mergeinfo(ra_session,
391 &repos_mergeinfo,
392 repos_rel_path,
393 target_rev,
394 inherit,
395 TRUE,
396 pool));
397 if (repos_mergeinfo)
399 *target_mergeinfo = repos_mergeinfo;
400 *indirect = TRUE;
405 return SVN_NO_ERROR;
409 svn_error_t *
410 svn_client__mergeinfo_from_segments(svn_mergeinfo_t *mergeinfo_p,
411 apr_array_header_t *segments,
412 apr_pool_t *pool)
414 svn_mergeinfo_t mergeinfo = apr_hash_make(pool);
415 int i;
417 /* Translate location segments into merge sources and ranges. */
418 for (i = 0; i < segments->nelts; i++)
420 svn_location_segment_t *segment =
421 APR_ARRAY_IDX(segments, i, svn_location_segment_t *);
422 apr_array_header_t *path_ranges;
423 svn_merge_range_t *range;
424 const char *source_path;
426 /* No path segment? Skip it. */
427 if (! segment->path)
428 continue;
430 /* Prepend a leading slash to our path. */
431 source_path = apr_pstrcat(pool, "/", segment->path, NULL);
433 /* See if we already stored ranges for this path. If not, make
434 a new list. */
435 path_ranges = apr_hash_get(mergeinfo, source_path, APR_HASH_KEY_STRING);
436 if (! path_ranges)
437 path_ranges = apr_array_make(pool, 1, sizeof(range));
439 /* Build a merge range, push it onto the list of ranges, and for
440 good measure, (re)store it in the hash. */
441 range = apr_pcalloc(pool, sizeof(*range));
442 range->start = MAX(segment->range_start - 1, 0);
443 range->end = segment->range_end;
444 range->inheritable = TRUE;
445 APR_ARRAY_PUSH(path_ranges, svn_merge_range_t *) = range;
446 apr_hash_set(mergeinfo, source_path, APR_HASH_KEY_STRING, path_ranges);
449 *mergeinfo_p = mergeinfo;
450 return SVN_NO_ERROR;
453 svn_error_t *
454 svn_client__get_history_as_mergeinfo(svn_mergeinfo_t *mergeinfo_p,
455 const char *path_or_url,
456 const svn_opt_revision_t *peg_revision,
457 svn_revnum_t range_youngest,
458 svn_revnum_t range_oldest,
459 svn_ra_session_t *ra_session,
460 svn_wc_adm_access_t *adm_access,
461 svn_client_ctx_t *ctx,
462 apr_pool_t *pool)
464 apr_array_header_t *segments;
465 svn_revnum_t peg_revnum = SVN_INVALID_REVNUM;
466 const char *url;
467 apr_pool_t *sesspool = NULL; /* only used for an RA session we open */
468 svn_ra_session_t *session = ra_session;
470 /* If PATH_OR_URL is a local path (not a URL), we need to transform
471 it into a URL, open an RA session for it, and resolve the peg
472 revision. Note that if the local item is scheduled for addition
473 as a copy of something else, we'll use its copyfrom data to query
474 its history. */
475 SVN_ERR(svn_client__derive_location(&url, &peg_revnum, path_or_url,
476 peg_revision, session, adm_access,
477 ctx, pool));
479 if (session == NULL)
481 sesspool = svn_pool_create(pool);
482 SVN_ERR(svn_client__open_ra_session_internal(&session, url, NULL, NULL,
483 NULL, FALSE, TRUE, ctx,
484 sesspool));
487 /* Fetch the location segments for our URL@PEG_REVNUM. */
488 if (! SVN_IS_VALID_REVNUM(range_youngest))
489 range_youngest = peg_revnum;
490 if (! SVN_IS_VALID_REVNUM(range_oldest))
491 range_oldest = 0;
492 SVN_ERR(svn_client__repos_location_segments(&segments, session, "",
493 peg_revnum, range_youngest,
494 range_oldest, ctx, pool));
496 SVN_ERR(svn_client__mergeinfo_from_segments(mergeinfo_p, segments, pool));
498 /* If we opened an RA session, ensure its closure. */
499 if (sesspool)
500 svn_pool_destroy(sesspool);
502 return SVN_NO_ERROR;
506 /*-----------------------------------------------------------------------*/
508 /*** Eliding mergeinfo. ***/
510 /* Given the mergeinfo (CHILD_MERGEINFO) for a path, and the
511 mergeinfo of its nearest ancestor with mergeinfo (PARENT_MERGEINFO), compare
512 CHILD_MERGEINFO to PARENT_MERGEINFO to see if the former elides to
513 the latter, following the elision rules described in
514 svn_client__elide_mergeinfo()'s docstring. Set *ELIDES to whether
515 or not CHILD_MERGEINFO is redundant.
517 Note: This function assumes that PARENT_MERGEINFO is definitive;
518 i.e. if it is NULL then the caller not only walked the entire WC
519 looking for inherited mergeinfo, but queried the repository if none
520 was found in the WC. This is rather important since this function
521 says empty mergeinfo should be elided if PARENT_MERGEINFO is NULL,
522 and we don't want to do that unless we are *certain* that the empty
523 mergeinfo on PATH isn't overriding anything.
525 If PATH_SUFFIX and PARENT_MERGEINFO are not NULL append PATH_SUFFIX
526 to each path in PARENT_MERGEINFO before performing the comparison. */
527 static svn_error_t *
528 should_elide_mergeinfo(svn_boolean_t *elides,
529 svn_mergeinfo_t parent_mergeinfo,
530 svn_mergeinfo_t child_mergeinfo,
531 const char *path_suffix,
532 apr_pool_t *pool)
534 /* Easy out: No child mergeinfo to elide. */
535 if (child_mergeinfo == NULL)
537 *elides = FALSE;
539 else if (apr_hash_count(child_mergeinfo) == 0)
541 /* Empty mergeinfo elides to empty mergeinfo or to "nothing",
542 i.e. it isn't overriding any parent. Otherwise it doesn't
543 elide. */
544 if (!parent_mergeinfo || apr_hash_count(parent_mergeinfo) == 0)
545 *elides = TRUE;
546 else
547 *elides = FALSE;
549 else if (!parent_mergeinfo || apr_hash_count(parent_mergeinfo) == 0)
551 /* Non-empty mergeinfo never elides to empty mergeinfo
552 or no mergeinfo. */
553 *elides = FALSE;
555 else
557 /* Both CHILD_MERGEINFO and PARENT_MERGEINFO are non-NULL and
558 non-empty. */
559 svn_mergeinfo_t path_tweaked_parent_mergeinfo;
560 apr_pool_t *subpool = svn_pool_create(pool);
562 path_tweaked_parent_mergeinfo = apr_hash_make(subpool);
564 /* If we need to adjust the paths in PARENT_MERGEINFO do it now. */
565 if (path_suffix)
566 adjust_mergeinfo_source_paths(path_tweaked_parent_mergeinfo,
567 path_suffix, parent_mergeinfo,
568 subpool);
569 else
570 path_tweaked_parent_mergeinfo = parent_mergeinfo;
572 SVN_ERR(svn_mergeinfo__equals(elides,
573 path_tweaked_parent_mergeinfo,
574 child_mergeinfo, TRUE, subpool));
575 svn_pool_destroy(subpool);
578 return SVN_NO_ERROR;
581 /* Helper for svn_client__elide_mergeinfo() and svn_client__elide_children().
583 Given a working copy PATH, its mergeinfo hash CHILD_MERGEINFO, and
584 the mergeinfo of PATH's nearest ancestor PARENT_MERGEINFO, use
585 should_elide_mergeinfo() to decide whether or not CHILD_MERGEINFO elides to
586 PARENT_MERGEINFO; PATH_SUFFIX means the same as in that function.
588 If elision does occur, then update the mergeinfo for PATH (which is
589 the child) in the working copy via ADM_ACCESS appropriately.
591 If CHILD_MERGEINFO is NULL, do nothing.
593 static svn_error_t *
594 elide_mergeinfo(svn_mergeinfo_t parent_mergeinfo,
595 svn_mergeinfo_t child_mergeinfo,
596 const char *path,
597 const char *path_suffix,
598 svn_wc_adm_access_t *adm_access,
599 apr_pool_t *pool)
601 svn_boolean_t elides;
602 SVN_ERR(should_elide_mergeinfo(&elides,
603 parent_mergeinfo, child_mergeinfo,
604 path_suffix, pool));
606 if (elides)
607 SVN_ERR(svn_wc_prop_set2(SVN_PROP_MERGEINFO, NULL, path, adm_access,
608 TRUE, pool));
610 return SVN_NO_ERROR;
614 svn_error_t *
615 svn_client__elide_children(apr_array_header_t *children_with_mergeinfo,
616 const char *target_wcpath,
617 const svn_wc_entry_t *entry,
618 svn_wc_adm_access_t *adm_access,
619 svn_client_ctx_t *ctx,
620 apr_pool_t *pool)
622 if (children_with_mergeinfo && children_with_mergeinfo->nelts)
624 int i;
625 const char *last_immediate_child;
626 svn_mergeinfo_t target_mergeinfo;
627 apr_pool_t *iterpool = svn_pool_create(pool);
629 /* Get mergeinfo for the target of the merge. */
630 SVN_ERR(svn_client__parse_mergeinfo(&target_mergeinfo, entry,
631 target_wcpath, FALSE,
632 adm_access, ctx, pool));
634 /* For each immediate child of the merge target check if
635 its merginfo elides to the target. */
636 for (i = 0; i < children_with_mergeinfo->nelts; i++)
638 svn_mergeinfo_t child_mergeinfo;
639 svn_boolean_t switched;
640 const svn_wc_entry_t *child_entry;
641 svn_client__merge_path_t *child =
642 APR_ARRAY_IDX(children_with_mergeinfo, i,
643 svn_client__merge_path_t *);
644 svn_pool_clear(iterpool);
646 if (!child)
647 continue;
649 if (child->absent)
650 continue;
652 if (i == 0)
654 /* children_with_mergeinfo is sorted depth
655 first so first path might be the target of
656 the merge if the target had mergeinfo prior
657 to the start of the merge. */
658 if (strcmp(target_wcpath, child->path) == 0)
660 last_immediate_child = NULL;
661 continue;
663 last_immediate_child = child->path;
665 else if (last_immediate_child
666 && svn_path_is_ancestor(last_immediate_child, child->path))
668 /* Not an immediate child. */
669 continue;
671 else
673 /* Found the first (last_immediate_child == NULL)
674 or another immediate child. */
675 last_immediate_child = child->path;
678 /* Don't try to elide switched children. */
679 SVN_ERR(svn_wc__entry_versioned(&child_entry, child->path,
680 adm_access, FALSE, iterpool));
681 SVN_ERR(svn_wc__path_switched(child->path, &switched, child_entry,
682 iterpool));
683 if (!switched)
685 const char *path_prefix = svn_path_dirname(child->path,
686 iterpool);
687 const char *path_suffix = svn_path_basename(child->path,
688 iterpool);
690 SVN_ERR(svn_client__parse_mergeinfo(&child_mergeinfo, entry,
691 child->path, FALSE,
692 adm_access, ctx, iterpool));
694 while (strcmp(path_prefix, target_wcpath) != 0)
696 path_suffix = svn_path_join(svn_path_basename(path_prefix,
697 iterpool),
698 path_suffix, iterpool);
699 path_prefix = svn_path_dirname(path_prefix, iterpool);
702 SVN_ERR(elide_mergeinfo(target_mergeinfo, child_mergeinfo,
703 child->path, path_suffix, adm_access,
704 iterpool));
707 svn_pool_destroy(iterpool);
710 return SVN_NO_ERROR;
714 svn_error_t *
715 svn_client__elide_mergeinfo(const char *target_wcpath,
716 const char *wc_elision_limit_path,
717 const svn_wc_entry_t *entry,
718 svn_wc_adm_access_t *adm_access,
719 svn_client_ctx_t *ctx,
720 apr_pool_t *pool)
722 /* Check for first easy out: We are already at the limit path. */
723 if (!wc_elision_limit_path
724 || strcmp(target_wcpath, wc_elision_limit_path) != 0)
726 svn_mergeinfo_t target_mergeinfo;
727 svn_mergeinfo_t mergeinfo = NULL;
728 svn_boolean_t inherited, switched;
729 const char *walk_path;
731 /* Check for second easy out: TARGET_WCPATH is switched. */
732 SVN_ERR(svn_wc__path_switched(target_wcpath, &switched, entry, pool));
733 if (!switched)
735 /* Get the TARGET_WCPATH's explicit mergeinfo. */
736 SVN_ERR(svn_client__get_wc_mergeinfo(&target_mergeinfo, &inherited,
737 FALSE, svn_mergeinfo_inherited,
738 entry, target_wcpath,
739 wc_elision_limit_path
740 ? wc_elision_limit_path
741 : NULL,
742 &walk_path, adm_access,
743 ctx, pool));
745 /* If TARGET_WCPATH has no explicit mergeinfo, there's nothing to
746 elide, we're done. */
747 if (inherited || target_mergeinfo == NULL)
748 return SVN_NO_ERROR;
750 /* Get TARGET_WCPATH's inherited mergeinfo from the WC. */
751 SVN_ERR(svn_client__get_wc_mergeinfo(&mergeinfo, &inherited, FALSE,
752 svn_mergeinfo_nearest_ancestor,
753 entry, target_wcpath,
754 wc_elision_limit_path
755 ? wc_elision_limit_path
756 : NULL,
757 &walk_path, adm_access,
758 ctx, pool));
760 /* If TARGET_WCPATH inherited no mergeinfo from the WC and we are
761 not limiting our search to the working copy then check if it
762 inherits any from the repos. */
763 if (!mergeinfo && !wc_elision_limit_path)
765 SVN_ERR(svn_client__get_wc_or_repos_mergeinfo
766 (&mergeinfo, entry, &inherited, TRUE,
767 svn_mergeinfo_nearest_ancestor,
768 NULL, target_wcpath, adm_access, ctx, pool));
771 /* If there is nowhere to elide TARGET_WCPATH's mergeinfo to and
772 the elision is limited, then we are done.*/
773 if (!mergeinfo && wc_elision_limit_path)
774 return SVN_NO_ERROR;
776 SVN_ERR(elide_mergeinfo(mergeinfo, target_mergeinfo, target_wcpath,
777 NULL, adm_access, pool));
780 return SVN_NO_ERROR;
783 svn_error_t *
784 svn_client__elide_mergeinfo_for_tree(apr_hash_t *children_with_mergeinfo,
785 svn_wc_adm_access_t *adm_access,
786 svn_client_ctx_t *ctx,
787 apr_pool_t *pool)
789 int i;
790 apr_pool_t *iterpool = svn_pool_create(pool);
791 apr_array_header_t *sorted_children =
792 svn_sort__hash(children_with_mergeinfo, svn_sort_compare_items_as_paths,
793 pool);
795 /* sorted_children is in depth first order. To minimize
796 svn_client__elide_mergeinfo()'s crawls up the working copy from
797 each child, run through the array backwards, effectively doing a
798 right-left post-order traversal. */
799 for (i = sorted_children->nelts -1; i >= 0; i--)
801 const svn_wc_entry_t *child_entry;
802 const char *child_wcpath;
803 svn_sort__item_t *item = &APR_ARRAY_IDX(sorted_children, i,
804 svn_sort__item_t);
805 svn_pool_clear(iterpool);
806 child_wcpath = item->key;
807 SVN_ERR(svn_wc__entry_versioned(&child_entry, child_wcpath, adm_access,
808 FALSE, iterpool));
809 SVN_ERR(svn_client__elide_mergeinfo(child_wcpath, NULL, child_entry,
810 adm_access, ctx, iterpool));
813 svn_pool_destroy(iterpool);
814 return SVN_NO_ERROR;
818 /* If the server supports Merge Tracking, set *MERGEINFO to a hash
819 mapping const char * root-relative source paths to an
820 apr_array_header_t * list of svn_merge_range_t * revision ranges
821 representing merge sources and corresponding revision ranges which
822 have been merged into PATH_OR_URL as of PEG_REVISION, or NULL if
823 there is no mergeinfo. Set *REPOS_ROOT to the root URL of the
824 repository associated with PATH_OR_URL (and to which the paths in
825 *MERGEINFO are relative). If the server does not support Merge Tracking,
826 return an error with the code SVN_ERR_UNSUPPORTED_FEATURE. Use
827 POOL for allocation of all returned values. */
828 static svn_error_t *
829 get_mergeinfo(svn_mergeinfo_t *mergeinfo,
830 const char **repos_root,
831 const char *path_or_url,
832 const svn_opt_revision_t *peg_revision,
833 svn_client_ctx_t *ctx,
834 apr_pool_t *pool)
836 apr_pool_t *subpool = svn_pool_create(pool);
837 svn_ra_session_t *ra_session;
838 svn_revnum_t rev;
840 if (svn_path_is_url(path_or_url))
842 const char *repos_rel_path;
844 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, path_or_url,
845 NULL, NULL, NULL, FALSE,
846 TRUE, ctx, subpool));
847 SVN_ERR(svn_client__get_revision_number(&rev, NULL, ra_session,
848 peg_revision, "", subpool));
849 SVN_ERR(svn_ra_get_repos_root2(ra_session, repos_root, pool));
850 SVN_ERR(svn_client__path_relative_to_root(&repos_rel_path, path_or_url,
851 *repos_root, FALSE, NULL,
852 NULL, subpool));
853 SVN_ERR(svn_client__get_repos_mergeinfo(ra_session, mergeinfo,
854 repos_rel_path, rev,
855 svn_mergeinfo_inherited, FALSE,
856 pool));
858 else /* ! svn_path_is_url() */
860 svn_wc_adm_access_t *adm_access;
861 const svn_wc_entry_t *entry;
862 const char *url;
863 svn_boolean_t indirect;
865 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, path_or_url, FALSE,
866 0, ctx->cancel_func, ctx->cancel_baton,
867 subpool));
868 SVN_ERR(svn_wc__entry_versioned(&entry, path_or_url, adm_access, FALSE,
869 subpool));
871 /* Check server Merge Tracking capability. */
872 SVN_ERR(svn_client__entry_location(&url, &rev, path_or_url,
873 svn_opt_revision_working, entry,
874 subpool));
875 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url,
876 NULL, NULL, NULL, FALSE,
877 TRUE, ctx, subpool));
878 SVN_ERR(svn_ra__assert_mergeinfo_capable_server(ra_session, path_or_url,
879 subpool));
881 /* Acquire return values. */
882 SVN_ERR(svn_client__get_repos_root(repos_root, path_or_url, peg_revision,
883 adm_access, ctx, pool));
884 SVN_ERR(svn_client__get_wc_or_repos_mergeinfo(mergeinfo, entry,
885 &indirect, FALSE,
886 svn_mergeinfo_inherited,
887 NULL, path_or_url,
888 adm_access, ctx, pool));
889 SVN_ERR(svn_wc_adm_close(adm_access));
892 svn_pool_destroy(subpool);
893 return SVN_NO_ERROR;
896 /*** In-memory mergeinfo elision ***/
898 /* TODO(reint): Document. */
899 struct elide_mergeinfo_catalog_dir_baton {
900 const char *inherited_mergeinfo_path;
901 svn_mergeinfo_t mergeinfo_catalog;
904 /* The root doesn't have mergeinfo (unless it is actually one of the
905 paths passed to svn_delta_path_driver, in which case the callback
906 is called directly instead of this). */
907 static svn_error_t *
908 elide_mergeinfo_catalog_open_root(void *eb,
909 svn_revnum_t base_revision,
910 apr_pool_t *dir_pool,
911 void **root_baton)
913 struct elide_mergeinfo_catalog_dir_baton *b = apr_pcalloc(dir_pool,
914 sizeof(*b));
915 b->mergeinfo_catalog = eb;
916 *root_baton = b;
917 return SVN_NO_ERROR;
920 /* Make a directory baton for PATH. It should have the same
921 inherited_mergeinfo_path as its parent... unless we just called
922 elide_mergeinfo_catalog_cb on its parent with its path. */
923 static svn_error_t *
924 elide_mergeinfo_catalog_open_directory(const char *path,
925 void *parent_baton,
926 svn_revnum_t base_revision,
927 apr_pool_t *dir_pool,
928 void **child_baton)
930 struct elide_mergeinfo_catalog_dir_baton *b, *pb = parent_baton;
932 b = apr_pcalloc(dir_pool, sizeof(*b));
933 b->mergeinfo_catalog = pb->mergeinfo_catalog;
935 if (apr_hash_get(b->mergeinfo_catalog, path, APR_HASH_KEY_STRING))
936 b->inherited_mergeinfo_path = apr_pstrdup(dir_pool, path);
937 else
938 b->inherited_mergeinfo_path = pb->inherited_mergeinfo_path;
940 *child_baton = b;
941 return SVN_NO_ERROR;
944 /* TODO(reint): Document. */
945 struct elide_mergeinfo_catalog_cb_baton {
946 apr_array_header_t *elidable_paths;
947 svn_mergeinfo_t mergeinfo_catalog;
948 apr_pool_t *result_pool;
951 /* Implements svn_delta_path_driver_cb_func_t. */
952 static svn_error_t *
953 elide_mergeinfo_catalog_cb(void **dir_baton,
954 void *parent_baton,
955 void *callback_baton,
956 const char *path,
957 apr_pool_t *pool)
959 struct elide_mergeinfo_catalog_cb_baton *cb = callback_baton;
960 struct elide_mergeinfo_catalog_dir_baton *pb = parent_baton;
961 const char *path_suffix;
962 svn_boolean_t elides;
964 /* pb == NULL would imply that there was an *empty* path in the
965 paths given to the driver (which is different from "/"). */
966 assert(pb != NULL);
968 /* We'll just act like everything is a file. */
969 *dir_baton = NULL;
971 /* Is there even any inherited mergeinfo to elide? */
972 /* (Note that svn_delta_path_driver will call open_directory before
973 the callback for the root (only).) */
974 if (!pb->inherited_mergeinfo_path
975 || strcmp(path, "/") == 0)
976 return SVN_NO_ERROR;
978 path_suffix = svn_path_is_child(pb->inherited_mergeinfo_path,
979 path, NULL);
980 assert(path_suffix != NULL);
982 SVN_ERR(should_elide_mergeinfo(&elides,
983 apr_hash_get(cb->mergeinfo_catalog,
984 pb->inherited_mergeinfo_path,
985 APR_HASH_KEY_STRING),
986 apr_hash_get(cb->mergeinfo_catalog,
987 path,
988 APR_HASH_KEY_STRING),
989 path_suffix,
990 pool));
992 if (elides)
993 APR_ARRAY_PUSH(cb->elidable_paths, const char *) =
994 apr_pstrdup(cb->result_pool, path);
996 return SVN_NO_ERROR;
999 svn_error_t *
1000 svn_client__elide_mergeinfo_catalog(svn_mergeinfo_t mergeinfo_catalog,
1001 apr_pool_t *pool)
1003 apr_array_header_t *paths;
1004 apr_array_header_t *elidable_paths = apr_array_make(pool, 1,
1005 sizeof(const char *));
1006 svn_delta_editor_t *editor = svn_delta_default_editor(pool);
1007 struct elide_mergeinfo_catalog_cb_baton cb = {elidable_paths,
1008 mergeinfo_catalog,
1009 pool};
1010 int i;
1012 editor->open_root = elide_mergeinfo_catalog_open_root;
1013 editor->open_directory = elide_mergeinfo_catalog_open_directory;
1015 /* Walk over the paths, and build up a list of elidable ones. */
1016 SVN_ERR(svn_hash_keys(&paths, mergeinfo_catalog, pool));
1017 SVN_ERR(svn_delta_path_driver(editor,
1018 mergeinfo_catalog, /* as edit_baton */
1019 SVN_INVALID_REVNUM,
1020 paths,
1021 elide_mergeinfo_catalog_cb,
1022 &cb,
1023 pool));
1025 /* Now remove the elidable paths from the catalog. */
1026 for (i = 0; i < elidable_paths->nelts; i++)
1028 const char *path = APR_ARRAY_IDX(elidable_paths, i, const char *);
1029 apr_hash_set(mergeinfo_catalog, path, APR_HASH_KEY_STRING, NULL);
1032 return SVN_NO_ERROR;
1036 struct filter_log_entry_baton_t
1038 apr_array_header_t *rangelist;
1039 svn_log_entry_receiver_t log_receiver;
1040 void *log_receiver_baton;
1041 svn_client_ctx_t *ctx;
1044 /* Implements the svn_log_entry_receiver_t interface. BATON is a
1045 `struct filter_log_entry_baton_t *' */
1046 static svn_error_t *
1047 filter_log_entry_with_rangelist(void *baton,
1048 svn_log_entry_t *log_entry,
1049 apr_pool_t *pool)
1051 struct filter_log_entry_baton_t *fleb = baton;
1052 svn_merge_range_t *range;
1053 apr_array_header_t *intersection, *this_rangelist;
1055 if (fleb->ctx->cancel_func)
1056 SVN_ERR(fleb->ctx->cancel_func(fleb->ctx->cancel_baton));
1058 this_rangelist = apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
1059 range = apr_pcalloc(pool, sizeof(*range));
1060 range->start = log_entry->revision - 1;
1061 range->end = log_entry->revision;
1062 range->inheritable = TRUE;
1063 APR_ARRAY_PUSH(this_rangelist, svn_merge_range_t *) = range;
1064 SVN_ERR(svn_rangelist_intersect(&intersection, fleb->rangelist,
1065 this_rangelist, pool));
1066 if (! (intersection && intersection->nelts))
1067 return SVN_NO_ERROR;
1069 assert (intersection->nelts == 1);
1070 return fleb->log_receiver(fleb->log_receiver_baton, log_entry, pool);
1073 static svn_error_t *
1074 logs_for_mergeinfo_rangelist(const char *source_url,
1075 apr_array_header_t *rangelist,
1076 svn_boolean_t discover_changed_paths,
1077 const apr_array_header_t *revprops,
1078 svn_log_entry_receiver_t log_receiver,
1079 void *log_receiver_baton,
1080 svn_client_ctx_t *ctx,
1081 apr_pool_t *pool)
1083 apr_array_header_t *target;
1084 svn_merge_range_t *oldest_range, *youngest_range;
1085 svn_opt_revision_t oldest_rev, youngest_rev;
1086 struct filter_log_entry_baton_t fleb;
1088 if (! rangelist->nelts)
1089 return SVN_NO_ERROR;
1091 /* Sort the rangelist. */
1092 qsort(rangelist->elts, rangelist->nelts,
1093 rangelist->elt_size, svn_sort_compare_ranges);
1095 /* Build a single-member log target list using SOURCE_URL. */
1096 target = apr_array_make(pool, 1, sizeof(const char *));
1097 APR_ARRAY_PUSH(target, const char *) = source_url;
1099 /* Calculate and construct the bounds of our log request. */
1100 youngest_range = APR_ARRAY_IDX(rangelist, rangelist->nelts - 1,
1101 svn_merge_range_t *);
1102 youngest_rev.kind = svn_opt_revision_number;
1103 youngest_rev.value.number = youngest_range->end;
1104 oldest_range = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *);
1105 oldest_rev.kind = svn_opt_revision_number;
1106 oldest_rev.value.number = oldest_range->start;
1108 /* Build the log filtering callback baton. */
1109 fleb.rangelist = rangelist;
1110 fleb.log_receiver = log_receiver;
1111 fleb.log_receiver_baton = log_receiver_baton;
1112 fleb.ctx = ctx;
1114 /* Drive the log. */
1115 SVN_ERR(svn_client_log4(target, &youngest_rev, &oldest_rev, &youngest_rev,
1116 0, discover_changed_paths, FALSE, FALSE, revprops,
1117 filter_log_entry_with_rangelist, &fleb, ctx, pool));
1119 /* Check for cancellation. */
1120 if (ctx->cancel_func)
1121 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1123 return SVN_NO_ERROR;
1127 /*** Public APIs ***/
1129 svn_error_t *
1130 svn_client_mergeinfo_log_merged(const char *path_or_url,
1131 const svn_opt_revision_t *peg_revision,
1132 const char *merge_source_url,
1133 const svn_opt_revision_t *src_peg_revision,
1134 svn_log_entry_receiver_t log_receiver,
1135 void *log_receiver_baton,
1136 svn_boolean_t discover_changed_paths,
1137 const apr_array_header_t *revprops,
1138 svn_client_ctx_t *ctx,
1139 apr_pool_t *pool)
1141 const char *repos_root, *log_target = NULL;
1142 svn_mergeinfo_t tgt_mergeinfo, source_history, mergeinfo;
1143 apr_array_header_t *rangelist;
1144 apr_hash_index_t *hi;
1145 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
1147 assert(svn_path_is_url(merge_source_url));
1149 /* Step 1: We need the union of PATH_OR_URL@PEG_REVISION's mergeinfo
1150 and MERGE_SOURCE_URL's history. It's not enough to do path
1151 matching, because renames in the history of MERGE_SOURCE_URL
1152 throw that all in a tizzy. Of course, if there's no mergeinfo on
1153 the target, that vastly simplifies matters (we'll have nothing to
1154 do). */
1155 /* This get_mergeinfo() call doubles as a mergeinfo capabilities check. */
1156 SVN_ERR(get_mergeinfo(&tgt_mergeinfo, &repos_root, path_or_url,
1157 peg_revision, ctx, pool));
1158 if (! tgt_mergeinfo)
1159 return SVN_NO_ERROR;
1160 SVN_ERR(svn_client__get_history_as_mergeinfo(&source_history,
1161 merge_source_url,
1162 src_peg_revision,
1163 SVN_INVALID_REVNUM,
1164 SVN_INVALID_REVNUM,
1165 NULL, NULL, ctx, pool));
1166 SVN_ERR(svn_mergeinfo_intersect(&mergeinfo, tgt_mergeinfo,
1167 source_history, pool));
1169 /* Step 2: Now, we iterate over the eligible paths/rangelists to
1170 find the youngest revision (and its associated path). Because
1171 SOURCE_HISTORY had the property that a revision could appear in
1172 at most one mergeinfo path, that same property is true of
1173 MERGEINFO (which is a subset of SOURCE_HISTORY). We'll use this
1174 information to bound a run of the logs of the source's history so
1175 we can filter out no-op merge revisions. While here, we'll
1176 collapse our rangelists into a single one. */
1177 rangelist = apr_array_make(pool, 64, sizeof(svn_merge_range_t *));
1178 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1180 const void *key;
1181 void *val;
1182 svn_merge_range_t *range;
1183 apr_array_header_t *list;
1185 apr_hash_this(hi, &key, NULL, &val);
1186 list = val;
1187 range = APR_ARRAY_IDX(list, list->nelts - 1, svn_merge_range_t *);
1188 if ((! SVN_IS_VALID_REVNUM(youngest_rev))
1189 || (range->end > youngest_rev))
1191 youngest_rev = range->end;
1192 log_target = key;
1194 SVN_ERR(svn_rangelist_merge(&rangelist, list, pool));
1197 /* Nothing eligible? Get outta here. */
1198 if (! rangelist->nelts)
1199 return SVN_NO_ERROR;
1201 /* Step 3: Finally, we run 'svn log' to drive our log receiver, but
1202 using a receiver filter to only allow revisions to pass through
1203 that are in our rangelist. */
1204 log_target = svn_path_url_add_component(repos_root, log_target + 1, pool);
1205 return logs_for_mergeinfo_rangelist(log_target, rangelist,
1206 discover_changed_paths, revprops,
1207 log_receiver, log_receiver_baton,
1208 ctx, pool);
1212 svn_error_t *
1213 svn_client_mergeinfo_get_merged(apr_hash_t **mergeinfo_p,
1214 const char *path_or_url,
1215 const svn_opt_revision_t *peg_revision,
1216 svn_client_ctx_t *ctx,
1217 apr_pool_t *pool)
1219 const char *repos_root;
1220 apr_hash_t *full_path_mergeinfo;
1221 svn_mergeinfo_t mergeinfo;
1223 SVN_ERR(get_mergeinfo(&mergeinfo, &repos_root, path_or_url,
1224 peg_revision, ctx, pool));
1226 /* Copy the MERGEINFO hash items into another hash, but change
1227 the relative paths into full URLs. */
1228 *mergeinfo_p = NULL;
1229 if (mergeinfo)
1231 apr_hash_index_t *hi;
1233 full_path_mergeinfo = apr_hash_make(pool);
1234 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1236 const void *key;
1237 void *val;
1238 const char *source_url;
1240 apr_hash_this(hi, &key, NULL, &val);
1241 source_url = svn_path_uri_encode(key, pool);
1242 source_url = svn_path_join(repos_root, source_url + 1, pool);
1243 apr_hash_set(full_path_mergeinfo, source_url,
1244 APR_HASH_KEY_STRING, val);
1246 *mergeinfo_p = full_path_mergeinfo;
1249 return SVN_NO_ERROR;
1253 svn_error_t *
1254 svn_client_mergeinfo_log_eligible(const char *path_or_url,
1255 const svn_opt_revision_t *peg_revision,
1256 const char *merge_source_url,
1257 const svn_opt_revision_t *src_peg_revision,
1258 svn_log_entry_receiver_t log_receiver,
1259 void *log_receiver_baton,
1260 svn_boolean_t discover_changed_paths,
1261 const apr_array_header_t *revprops,
1262 svn_client_ctx_t *ctx,
1263 apr_pool_t *pool)
1265 svn_mergeinfo_t mergeinfo, history, source_history, available;
1266 apr_hash_index_t *hi;
1267 svn_ra_session_t *ra_session;
1268 const char *repos_root;
1269 apr_pool_t *sesspool;
1270 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
1271 apr_array_header_t *rangelist;
1272 const char *log_target = NULL;
1274 assert(svn_path_is_url(merge_source_url));
1276 /* Step 1: Across the set of possible merges, see what's already
1277 been merged into PATH_OR_URL@PEG_REVISION (or what's already part
1278 of the history it shares with that of MERGE_SOURCE_URL. */
1279 /* This get_mergeinfo() call doubles as a mergeinfo capabilities check. */
1280 SVN_ERR(get_mergeinfo(&mergeinfo, &repos_root, path_or_url,
1281 peg_revision, ctx, pool));
1282 SVN_ERR(svn_client__get_history_as_mergeinfo(&history,
1283 path_or_url,
1284 peg_revision,
1285 SVN_INVALID_REVNUM,
1286 SVN_INVALID_REVNUM,
1287 NULL, NULL, ctx, pool));
1288 if (! mergeinfo)
1289 mergeinfo = history;
1290 else
1291 svn_mergeinfo_merge(mergeinfo, history, pool);
1293 /* Step 2: See what merge sources can be derived from the history of
1294 MERGE_SOURCE_URL. */
1295 sesspool = svn_pool_create(pool);
1296 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, merge_source_url,
1297 NULL, NULL, NULL, FALSE,
1298 TRUE, ctx, sesspool));
1299 SVN_ERR(svn_client__get_history_as_mergeinfo(&source_history,
1300 merge_source_url,
1301 src_peg_revision,
1302 SVN_INVALID_REVNUM,
1303 SVN_INVALID_REVNUM,
1304 ra_session, NULL, ctx, pool));
1305 svn_pool_destroy(sesspool);
1307 /* Now, we want to remove from the possible mergeinfo
1308 (SOURCE_HISTORY) the merges already present in our PATH_OR_URL. */
1309 SVN_ERR(svn_mergeinfo_remove(&available, mergeinfo, source_history, pool));
1311 /* Step 3: Now, we iterate over the eligible paths/rangelists to
1312 find the youngest revision (and its associated path). Because
1313 SOURCE_HISTORY had the property that a revision could appear in
1314 at most one mergeinfo path, that same property is true of
1315 AVAILABLE (which is a subset of SOURCE_HISTORY). We'll use this
1316 information to bound a run of the logs of the source's history so
1317 we can filter out no-op merge revisions. While here, we'll
1318 collapse our rangelists into a single one. */
1319 rangelist = apr_array_make(pool, 64, sizeof(svn_merge_range_t *));
1320 for (hi = apr_hash_first(pool, available); hi; hi = apr_hash_next(hi))
1322 const void *key;
1323 void *val;
1324 svn_merge_range_t *range;
1325 apr_array_header_t *list;
1327 apr_hash_this(hi, &key, NULL, &val);
1328 list = val;
1329 range = APR_ARRAY_IDX(list, list->nelts - 1, svn_merge_range_t *);
1330 if ((! SVN_IS_VALID_REVNUM(youngest_rev))
1331 || (range->end > youngest_rev))
1333 youngest_rev = range->end;
1334 log_target = key;
1336 SVN_ERR(svn_rangelist_merge(&rangelist, list, pool));
1339 /* Nothing eligible? Get outta here. */
1340 if (! rangelist->nelts)
1341 return SVN_NO_ERROR;
1343 /* Step 4: Finally, we run 'svn log' to drive our log receiver, but
1344 using a receiver filter to only allow revisions to pass through
1345 that are in our rangelist. */
1346 log_target = svn_path_url_add_component(repos_root, log_target + 1, pool);
1347 return logs_for_mergeinfo_rangelist(log_target, rangelist,
1348 discover_changed_paths, revprops,
1349 log_receiver, log_receiver_baton,
1350 ctx, pool);
1354 svn_error_t *
1355 svn_client_suggest_merge_sources(apr_array_header_t **suggestions,
1356 const char *path_or_url,
1357 const svn_opt_revision_t *peg_revision,
1358 svn_client_ctx_t *ctx,
1359 apr_pool_t *pool)
1361 const char *repos_root;
1362 const char *copyfrom_path;
1363 apr_array_header_t *list;
1364 svn_revnum_t copyfrom_rev;
1365 svn_mergeinfo_t mergeinfo;
1366 apr_hash_index_t *hi;
1368 list = apr_array_make(pool, 1, sizeof(const char *));
1370 /* In our ideal algorithm, the list of recommendations should be
1371 ordered by:
1373 1. The most recent existing merge source.
1374 2. The copyfrom source (which will also be listed as a merge
1375 source if the copy was made with a 1.5+ client and server).
1376 3. All other merge sources, most recent to least recent.
1378 However, determining the order of application of merge sources
1379 requires a new RA API. Until such an API is available, our
1380 algorithm will be:
1382 1. The copyfrom source.
1383 2. All remaining merge sources (unordered).
1386 /* ### TODO: Share ra_session batons to improve efficiency? */
1387 SVN_ERR(get_mergeinfo(&mergeinfo, &repos_root, path_or_url,
1388 peg_revision, ctx, pool));
1389 SVN_ERR(svn_client__get_copy_source(path_or_url, peg_revision,
1390 &copyfrom_path, &copyfrom_rev,
1391 ctx, pool));
1392 if (copyfrom_path)
1394 APR_ARRAY_PUSH(list, const char *) =
1395 svn_path_url_add_component(repos_root, copyfrom_path + 1, pool);
1398 if (mergeinfo)
1400 for (hi = apr_hash_first(NULL, mergeinfo); hi; hi = apr_hash_next(hi))
1402 const void *key;
1403 const char *rel_path;
1405 apr_hash_this(hi, &key, NULL, NULL);
1406 rel_path = key;
1407 if (copyfrom_path == NULL || strcmp(rel_path, copyfrom_path) != 0)
1408 APR_ARRAY_PUSH(list, const char *) = \
1409 svn_path_url_add_component(repos_root, rel_path + 1, pool);
1413 *suggestions = list;
1414 return SVN_NO_ERROR;