1 /*
2 * mergeinfo.c : merge history functions for the libsvn_client library
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24 #include <apr_pools.h>
25 #include <apr_strings.h>
26
27 #include "svn_pools.h"
28 #include "svn_dirent_uri.h"
29 #include "svn_path.h"
30 #include "svn_string.h"
31 #include "svn_opt.h"
32 #include "svn_error.h"
33 #include "svn_error_codes.h"
34 #include "svn_props.h"
35 #include "svn_mergeinfo.h"
36 #include "svn_sorts.h"
37 #include "svn_ra.h"
38 #include "svn_client.h"
39 #include "svn_hash.h"
40
41 #include "private/svn_opt_private.h"
42 #include "private/svn_mergeinfo_private.h"
43 #include "private/svn_wc_private.h"
44 #include "private/svn_ra_private.h"
45 #include "private/svn_fspath.h"
46 #include "private/svn_client_private.h"
47 #include "client.h"
48 #include "mergeinfo.h"
49 #include "svn_private_config.h"
50
51
52
53 svn_client__merge_path_t *
svn_client__merge_path_dup(const svn_client__merge_path_t * old,apr_pool_t * pool)54 svn_client__merge_path_dup(const svn_client__merge_path_t *old,
55 apr_pool_t *pool)
56 {
57 svn_client__merge_path_t *new = apr_pmemdup(pool, old, sizeof(*old));
58
59 new->abspath = apr_pstrdup(pool, old->abspath);
60 if (new->remaining_ranges)
61 new->remaining_ranges = svn_rangelist_dup(old->remaining_ranges, pool);
62 if (new->pre_merge_mergeinfo)
63 new->pre_merge_mergeinfo = svn_mergeinfo_dup(old->pre_merge_mergeinfo,
64 pool);
65 if (new->implicit_mergeinfo)
66 new->implicit_mergeinfo = svn_mergeinfo_dup(old->implicit_mergeinfo,
67 pool);
68
69 return new;
70 }
71
72 svn_client__merge_path_t *
svn_client__merge_path_create(const char * abspath,apr_pool_t * pool)73 svn_client__merge_path_create(const char *abspath,
74 apr_pool_t *pool)
75 {
76 svn_client__merge_path_t *result = apr_pcalloc(pool, sizeof(*result));
77
78 result->abspath = apr_pstrdup(pool, abspath);
79 return result;
80 }
81
82 svn_error_t *
svn_client__parse_mergeinfo(svn_mergeinfo_t * mergeinfo,svn_wc_context_t * wc_ctx,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)83 svn_client__parse_mergeinfo(svn_mergeinfo_t *mergeinfo,
84 svn_wc_context_t *wc_ctx,
85 const char *local_abspath,
86 apr_pool_t *result_pool,
87 apr_pool_t *scratch_pool)
88 {
89 const svn_string_t *propval;
90
91 *mergeinfo = NULL;
92
93 /* ### Use svn_wc_prop_get() would actually be sufficient for now.
94 ### DannyB thinks that later we'll need behavior more like
95 ### svn_client__get_prop_from_wc(). */
96 SVN_ERR(svn_wc_prop_get2(&propval, wc_ctx, local_abspath, SVN_PROP_MERGEINFO,
97 scratch_pool, scratch_pool));
98 if (propval)
99 SVN_ERR(svn_mergeinfo_parse(mergeinfo, propval->data, result_pool));
100
101 return SVN_NO_ERROR;
102 }
103
104 svn_error_t *
svn_client__record_wc_mergeinfo(const char * local_abspath,svn_mergeinfo_t mergeinfo,svn_boolean_t do_notification,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)105 svn_client__record_wc_mergeinfo(const char *local_abspath,
106 svn_mergeinfo_t mergeinfo,
107 svn_boolean_t do_notification,
108 svn_client_ctx_t *ctx,
109 apr_pool_t *scratch_pool)
110 {
111 svn_string_t *mergeinfo_str = NULL;
112 svn_boolean_t mergeinfo_changes = FALSE;
113
114 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
115
116 /* Convert MERGEINFO (if any) into text for storage as a property value. */
117 if (mergeinfo)
118 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_str, mergeinfo, scratch_pool));
119
120 if (do_notification && ctx->notify_func2)
121 SVN_ERR(svn_client__mergeinfo_status(&mergeinfo_changes, ctx->wc_ctx,
122 local_abspath, scratch_pool));
123
124 /* Record the new mergeinfo in the WC. */
125 /* ### Later, we'll want behavior more analogous to
126 ### svn_client__get_prop_from_wc(). */
127 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, SVN_PROP_MERGEINFO,
128 mergeinfo_str, svn_depth_empty,
129 TRUE /* skip checks */, NULL,
130 NULL, NULL /* cancellation */,
131 NULL, NULL /* notification */,
132 scratch_pool));
133
134 if (do_notification && ctx->notify_func2)
135 {
136 svn_wc_notify_t *notify =
137 svn_wc_create_notify(local_abspath,
138 svn_wc_notify_merge_record_info,
139 scratch_pool);
140 if (mergeinfo_changes)
141 notify->prop_state = svn_wc_notify_state_merged;
142 else
143 notify->prop_state = svn_wc_notify_state_changed;
144
145 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
146 }
147
148 return SVN_NO_ERROR;
149 }
150
151 svn_error_t *
svn_client__record_wc_mergeinfo_catalog(apr_hash_t * result_catalog,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)152 svn_client__record_wc_mergeinfo_catalog(apr_hash_t *result_catalog,
153 svn_client_ctx_t *ctx,
154 apr_pool_t *scratch_pool)
155 {
156 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
157
158 if (apr_hash_count(result_catalog))
159 {
160 int i;
161 apr_array_header_t *sorted_cat =
162 svn_sort__hash(result_catalog, svn_sort_compare_items_as_paths,
163 scratch_pool);
164
165 /* Write the mergeinfo out in sorted order of the paths (presumably just
166 * so that the notifications are in a predictable, convenient order). */
167 for (i = 0; i < sorted_cat->nelts; i++)
168 {
169 svn_sort__item_t elt = APR_ARRAY_IDX(sorted_cat, i,
170 svn_sort__item_t);
171 svn_error_t *err;
172
173 svn_pool_clear(iterpool);
174 err = svn_client__record_wc_mergeinfo(elt.key, elt.value, TRUE,
175 ctx, iterpool);
176
177 if (err && err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
178 {
179 /* PATH isn't just missing, it's not even versioned as far
180 as this working copy knows. But it was included in
181 MERGES, which means that the server knows about it.
182 Likely we don't have access to the source due to authz
183 restrictions. For now just clear the error and
184 continue... */
185 svn_error_clear(err);
186 }
187 else
188 {
189 SVN_ERR(err);
190 }
191 }
192 }
193 svn_pool_destroy(iterpool);
194 return SVN_NO_ERROR;
195 }
196
197 /*-----------------------------------------------------------------------*/
198
199 /*** Retrieving mergeinfo. ***/
200
201 svn_error_t *
svn_client__get_wc_mergeinfo(svn_mergeinfo_t * mergeinfo,svn_boolean_t * inherited_p,svn_mergeinfo_inheritance_t inherit,const char * local_abspath,const char * limit_abspath,const char ** walked_path,svn_boolean_t ignore_invalid_mergeinfo,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)202 svn_client__get_wc_mergeinfo(svn_mergeinfo_t *mergeinfo,
203 svn_boolean_t *inherited_p,
204 svn_mergeinfo_inheritance_t inherit,
205 const char *local_abspath,
206 const char *limit_abspath,
207 const char **walked_path,
208 svn_boolean_t ignore_invalid_mergeinfo,
209 svn_client_ctx_t *ctx,
210 apr_pool_t *result_pool,
211 apr_pool_t *scratch_pool)
212 {
213 const char *walk_relpath = "";
214 svn_mergeinfo_t wc_mergeinfo;
215 svn_revnum_t base_revision;
216 apr_pool_t *iterpool;
217 svn_boolean_t inherited;
218
219 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
220 if (limit_abspath)
221 SVN_ERR_ASSERT(svn_dirent_is_absolute(limit_abspath));
222
223 SVN_ERR(svn_wc__node_get_base(NULL, &base_revision, NULL, NULL, NULL, NULL,
224 ctx->wc_ctx, local_abspath,
225 TRUE /* ignore_enoent */,
226 FALSE /* show_hidden */,
227 scratch_pool, scratch_pool));
228
229 iterpool = svn_pool_create(scratch_pool);
230 while (TRUE)
231 {
232 svn_pool_clear(iterpool);
233
234 /* Don't look for explicit mergeinfo on LOCAL_ABSPATH if we are only
235 interested in inherited mergeinfo. */
236 if (inherit == svn_mergeinfo_nearest_ancestor)
237 {
238 wc_mergeinfo = NULL;
239 inherit = svn_mergeinfo_inherited;
240 }
241 else
242 {
243 /* Look for mergeinfo on LOCAL_ABSPATH. If there isn't any and we
244 want inherited mergeinfo, walk towards the root of the WC until
245 we encounter either (a) an unversioned directory, or
246 (b) mergeinfo. If we encounter (b), use that inherited
247 mergeinfo as our baseline. */
248 svn_error_t *err = svn_client__parse_mergeinfo(&wc_mergeinfo,
249 ctx->wc_ctx,
250 local_abspath,
251 result_pool,
252 iterpool);
253 if ((ignore_invalid_mergeinfo || walk_relpath [0] != '\0')
254 && err
255 && err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
256 {
257 svn_error_clear(err);
258 wc_mergeinfo = apr_hash_make(result_pool);
259 break;
260 }
261 else
262 {
263 SVN_ERR(err);
264 }
265 }
266
267 if (wc_mergeinfo == NULL &&
268 inherit != svn_mergeinfo_explicit &&
269 !svn_dirent_is_root(local_abspath, strlen(local_abspath)))
270 {
271 svn_boolean_t is_wc_root;
272 svn_boolean_t is_switched;
273 svn_revnum_t parent_base_rev;
274 svn_revnum_t parent_changed_rev;
275
276 /* Don't look any higher than the limit path. */
277 if (limit_abspath && strcmp(limit_abspath, local_abspath) == 0)
278 break;
279
280 /* If we've reached the root of the working copy don't look any
281 higher. */
282 SVN_ERR(svn_wc_check_root(&is_wc_root, &is_switched, NULL,
283 ctx->wc_ctx, local_abspath, iterpool));
284 if (is_wc_root || is_switched)
285 break;
286
287 /* No explicit mergeinfo on this path. Look higher up the
288 directory tree while keeping track of what we've walked. */
289 walk_relpath = svn_relpath_join(svn_dirent_basename(local_abspath,
290 iterpool),
291 walk_relpath, result_pool);
292 local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
293
294 SVN_ERR(svn_wc__node_get_base(NULL, &parent_base_rev, NULL, NULL,
295 NULL, NULL,
296 ctx->wc_ctx, local_abspath,
297 TRUE, FALSE,
298 scratch_pool, scratch_pool));
299
300 /* ### This checks the WORKING changed_rev, so invalid on replacement
301 ### not even reliable in case an ancestor was copied from a
302 ### different location */
303 SVN_ERR(svn_wc__node_get_changed_info(&parent_changed_rev,
304 NULL, NULL,
305 ctx->wc_ctx, local_abspath,
306 scratch_pool,
307 scratch_pool));
308
309 /* Look in LOCAL_ABSPATH's parent for inherited mergeinfo if
310 LOCAL_ABSPATH has no base revision because it is an uncommitted
311 addition, or if its base revision falls within the inclusive
312 range of its parent's last changed revision to the parent's base
313 revision; otherwise stop looking for inherited mergeinfo. */
314 if (SVN_IS_VALID_REVNUM(base_revision)
315 && (base_revision < parent_changed_rev
316 || parent_base_rev < base_revision))
317 break;
318
319 /* We haven't yet risen above the root of the WC. */
320 continue;
321 }
322 break;
323 }
324
325 svn_pool_destroy(iterpool);
326
327 if (svn_path_is_empty(walk_relpath))
328 {
329 /* Mergeinfo is explicit. */
330 inherited = FALSE;
331 *mergeinfo = wc_mergeinfo;
332 }
333 else
334 {
335 /* Mergeinfo may be inherited. */
336 if (wc_mergeinfo)
337 {
338 inherited = TRUE;
339 SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(mergeinfo,
340 wc_mergeinfo,
341 walk_relpath,
342 result_pool,
343 scratch_pool));
344 }
345 else
346 {
347 inherited = FALSE;
348 *mergeinfo = NULL;
349 }
350 }
351
352 if (walked_path)
353 *walked_path = walk_relpath;
354
355 /* Remove non-inheritable mergeinfo and paths mapped to empty ranges
356 which may occur if WCPATH's mergeinfo is not explicit. */
357 if (inherited
358 && apr_hash_count(*mergeinfo)) /* Nothing to do for empty mergeinfo. */
359 {
360 SVN_ERR(svn_mergeinfo_inheritable2(mergeinfo, *mergeinfo, NULL,
361 SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
362 TRUE, result_pool, scratch_pool));
363 svn_mergeinfo__remove_empty_rangelists(*mergeinfo, result_pool);
364 }
365
366 if (inherited_p)
367 *inherited_p = inherited;
368
369 return SVN_NO_ERROR;
370 }
371
372 svn_error_t *
svn_client__get_wc_mergeinfo_catalog(svn_mergeinfo_catalog_t * mergeinfo_cat,svn_boolean_t * inherited,svn_boolean_t include_descendants,svn_mergeinfo_inheritance_t inherit,const char * local_abspath,const char * limit_path,const char ** walked_path,svn_boolean_t ignore_invalid_mergeinfo,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)373 svn_client__get_wc_mergeinfo_catalog(svn_mergeinfo_catalog_t *mergeinfo_cat,
374 svn_boolean_t *inherited,
375 svn_boolean_t include_descendants,
376 svn_mergeinfo_inheritance_t inherit,
377 const char *local_abspath,
378 const char *limit_path,
379 const char **walked_path,
380 svn_boolean_t ignore_invalid_mergeinfo,
381 svn_client_ctx_t *ctx,
382 apr_pool_t *result_pool,
383 apr_pool_t *scratch_pool)
384 {
385 const char *target_repos_relpath;
386 svn_mergeinfo_t mergeinfo;
387 const char *repos_root;
388
389 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
390 *mergeinfo_cat = NULL;
391 SVN_ERR(svn_wc__node_get_repos_info(NULL, &target_repos_relpath,
392 &repos_root, NULL,
393 ctx->wc_ctx, local_abspath,
394 scratch_pool, scratch_pool));
395
396 /* Get the mergeinfo for the LOCAL_ABSPATH target and set *INHERITED and
397 *WALKED_PATH. */
398 SVN_ERR(svn_client__get_wc_mergeinfo(&mergeinfo, inherited, inherit,
399 local_abspath, limit_path,
400 walked_path, ignore_invalid_mergeinfo,
401 ctx, result_pool, scratch_pool));
402
403 /* Add any explicit/inherited mergeinfo for LOCAL_ABSPATH to
404 *MERGEINFO_CAT. */
405 if (mergeinfo)
406 {
407 *mergeinfo_cat = apr_hash_make(result_pool);
408 svn_hash_sets(*mergeinfo_cat,
409 apr_pstrdup(result_pool, target_repos_relpath), mergeinfo);
410 }
411
412 /* If LOCAL_ABSPATH is a directory and we want the subtree mergeinfo too,
413 then get it.
414
415 With WC-NG it is cheaper to do a single db transaction, than first
416 looking if we really have a directory. */
417 if (include_descendants)
418 {
419 apr_hash_t *mergeinfo_props;
420 apr_hash_index_t *hi;
421
422 SVN_ERR(svn_wc__prop_retrieve_recursive(&mergeinfo_props,
423 ctx->wc_ctx, local_abspath,
424 SVN_PROP_MERGEINFO,
425 scratch_pool, scratch_pool));
426
427 /* Convert *mergeinfo_props into a proper svn_mergeinfo_catalog_t */
428 for (hi = apr_hash_first(scratch_pool, mergeinfo_props);
429 hi;
430 hi = apr_hash_next(hi))
431 {
432 const char *node_abspath = svn__apr_hash_index_key(hi);
433 svn_string_t *propval = svn__apr_hash_index_val(hi);
434 svn_mergeinfo_t subtree_mergeinfo;
435 const char *repos_relpath;
436
437 if (strcmp(node_abspath, local_abspath) == 0)
438 continue; /* Already parsed in svn_client__get_wc_mergeinfo */
439
440 SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, NULL, NULL,
441 ctx->wc_ctx, node_abspath,
442 result_pool, scratch_pool));
443
444 SVN_ERR(svn_mergeinfo_parse(&subtree_mergeinfo, propval->data,
445 result_pool));
446
447 /* If the target had no explicit/inherited mergeinfo and this is the
448 first subtree with mergeinfo found, then the catalog will still
449 be NULL. */
450 if (*mergeinfo_cat == NULL)
451 *mergeinfo_cat = apr_hash_make(result_pool);
452
453 svn_hash_sets(*mergeinfo_cat, repos_relpath, subtree_mergeinfo);
454 }
455 }
456
457 return SVN_NO_ERROR;
458 }
459
460 svn_error_t *
svn_client__get_repos_mergeinfo(svn_mergeinfo_t * target_mergeinfo,svn_ra_session_t * ra_session,const char * url,svn_revnum_t rev,svn_mergeinfo_inheritance_t inherit,svn_boolean_t squelch_incapable,apr_pool_t * pool)461 svn_client__get_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo,
462 svn_ra_session_t *ra_session,
463 const char *url,
464 svn_revnum_t rev,
465 svn_mergeinfo_inheritance_t inherit,
466 svn_boolean_t squelch_incapable,
467 apr_pool_t *pool)
468 {
469 svn_mergeinfo_catalog_t tgt_mergeinfo_cat;
470
471 *target_mergeinfo = NULL;
472
473 SVN_ERR(svn_client__get_repos_mergeinfo_catalog(&tgt_mergeinfo_cat,
474 ra_session,
475 url, rev, inherit,
476 squelch_incapable, FALSE,
477 pool, pool));
478
479 if (tgt_mergeinfo_cat && apr_hash_count(tgt_mergeinfo_cat))
480 {
481 /* We asked only for the REL_PATH's mergeinfo, not any of its
482 descendants. So if there is anything in the catalog it is the
483 mergeinfo for REL_PATH. */
484 *target_mergeinfo =
485 svn__apr_hash_index_val(apr_hash_first(pool, tgt_mergeinfo_cat));
486
487 }
488
489 return SVN_NO_ERROR;
490 }
491
492 svn_error_t *
svn_client__get_repos_mergeinfo_catalog(svn_mergeinfo_catalog_t * mergeinfo_cat,svn_ra_session_t * ra_session,const char * url,svn_revnum_t rev,svn_mergeinfo_inheritance_t inherit,svn_boolean_t squelch_incapable,svn_boolean_t include_descendants,apr_pool_t * result_pool,apr_pool_t * scratch_pool)493 svn_client__get_repos_mergeinfo_catalog(svn_mergeinfo_catalog_t *mergeinfo_cat,
494 svn_ra_session_t *ra_session,
495 const char *url,
496 svn_revnum_t rev,
497 svn_mergeinfo_inheritance_t inherit,
498 svn_boolean_t squelch_incapable,
499 svn_boolean_t include_descendants,
500 apr_pool_t *result_pool,
501 apr_pool_t *scratch_pool)
502 {
503 svn_error_t *err;
504 svn_mergeinfo_catalog_t repos_mergeinfo_cat;
505 apr_array_header_t *rel_paths = apr_array_make(scratch_pool, 1,
506 sizeof(const char *));
507 const char *old_session_url;
508
509 APR_ARRAY_PUSH(rel_paths, const char *) = "";
510
511 /* Fetch the mergeinfo. */
512 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url,
513 ra_session, url, scratch_pool));
514 err = svn_ra_get_mergeinfo(ra_session, &repos_mergeinfo_cat, rel_paths,
515 rev, inherit, include_descendants, result_pool);
516 err = svn_error_compose_create(
517 err, svn_ra_reparent(ra_session, old_session_url, scratch_pool));
518 if (err)
519 {
520 if (squelch_incapable && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
521 {
522 svn_error_clear(err);
523 *mergeinfo_cat = NULL;
524 return SVN_NO_ERROR;
525 }
526 else
527 return svn_error_trace(err);
528 }
529
530 if (repos_mergeinfo_cat == NULL)
531 {
532 *mergeinfo_cat = NULL;
533 }
534 else
535 {
536 const char *session_relpath;
537
538 SVN_ERR(svn_ra_get_path_relative_to_root(ra_session, &session_relpath,
539 url, scratch_pool));
540
541 if (session_relpath[0] == '\0')
542 *mergeinfo_cat = repos_mergeinfo_cat;
543 else
544 SVN_ERR(svn_mergeinfo__add_prefix_to_catalog(mergeinfo_cat,
545 repos_mergeinfo_cat,
546 session_relpath,
547 result_pool,
548 scratch_pool));
549 }
550 return SVN_NO_ERROR;
551 }
552
553
554 svn_error_t *
svn_client__get_wc_or_repos_mergeinfo(svn_mergeinfo_t * target_mergeinfo,svn_boolean_t * inherited,svn_boolean_t * from_repos,svn_boolean_t repos_only,svn_mergeinfo_inheritance_t inherit,svn_ra_session_t * ra_session,const char * target_wcpath,svn_client_ctx_t * ctx,apr_pool_t * pool)555 svn_client__get_wc_or_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo,
556 svn_boolean_t *inherited,
557 svn_boolean_t *from_repos,
558 svn_boolean_t repos_only,
559 svn_mergeinfo_inheritance_t inherit,
560 svn_ra_session_t *ra_session,
561 const char *target_wcpath,
562 svn_client_ctx_t *ctx,
563 apr_pool_t *pool)
564 {
565 svn_mergeinfo_catalog_t tgt_mergeinfo_cat;
566
567 *target_mergeinfo = NULL;
568
569 SVN_ERR(svn_client__get_wc_or_repos_mergeinfo_catalog(&tgt_mergeinfo_cat,
570 inherited, from_repos,
571 FALSE,
572 repos_only,
573 FALSE, inherit,
574 ra_session,
575 target_wcpath, ctx,
576 pool, pool));
577 if (tgt_mergeinfo_cat && apr_hash_count(tgt_mergeinfo_cat))
578 {
579 /* We asked only for the TARGET_WCPATH's mergeinfo, not any of its
580 descendants. It this mergeinfo is in the catalog, it's keyed
581 on TARGET_WCPATH's root-relative path. We could dig that up
582 so we can peek into our catalog, but it ought to be the only
583 thing in the catalog, so we'll just fetch the first hash item. */
584 *target_mergeinfo =
585 svn__apr_hash_index_val(apr_hash_first(pool, tgt_mergeinfo_cat));
586
587 }
588
589 return SVN_NO_ERROR;
590 }
591
592 svn_error_t *
svn_client__get_wc_or_repos_mergeinfo_catalog(svn_mergeinfo_catalog_t * target_mergeinfo_catalog,svn_boolean_t * inherited_p,svn_boolean_t * from_repos,svn_boolean_t include_descendants,svn_boolean_t repos_only,svn_boolean_t ignore_invalid_mergeinfo,svn_mergeinfo_inheritance_t inherit,svn_ra_session_t * ra_session,const char * target_wcpath,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)593 svn_client__get_wc_or_repos_mergeinfo_catalog(
594 svn_mergeinfo_catalog_t *target_mergeinfo_catalog,
595 svn_boolean_t *inherited_p,
596 svn_boolean_t *from_repos,
597 svn_boolean_t include_descendants,
598 svn_boolean_t repos_only,
599 svn_boolean_t ignore_invalid_mergeinfo,
600 svn_mergeinfo_inheritance_t inherit,
601 svn_ra_session_t *ra_session,
602 const char *target_wcpath,
603 svn_client_ctx_t *ctx,
604 apr_pool_t *result_pool,
605 apr_pool_t *scratch_pool)
606 {
607 const char *url;
608 svn_revnum_t target_rev;
609 const char *local_abspath;
610 const char *repos_root;
611 const char *repos_relpath;
612 svn_mergeinfo_catalog_t target_mergeinfo_cat_wc = NULL;
613 svn_mergeinfo_catalog_t target_mergeinfo_cat_repos = NULL;
614
615 SVN_ERR(svn_dirent_get_absolute(&local_abspath, target_wcpath,
616 scratch_pool));
617
618 if (from_repos)
619 *from_repos = FALSE;
620
621 /* We may get an entry with abbreviated information from TARGET_WCPATH's
622 parent if TARGET_WCPATH is missing. These limited entries do not have
623 a URL and without that we cannot get accurate mergeinfo for
624 TARGET_WCPATH. */
625 SVN_ERR(svn_wc__node_get_origin(NULL, &target_rev, &repos_relpath,
626 &repos_root, NULL, NULL,
627 ctx->wc_ctx, local_abspath, FALSE,
628 scratch_pool, scratch_pool));
629
630 if (repos_relpath)
631 url = svn_path_url_add_component2(repos_root, repos_relpath, scratch_pool);
632 else
633 url = NULL;
634
635 if (!repos_only)
636 {
637 svn_boolean_t inherited;
638 SVN_ERR(svn_client__get_wc_mergeinfo_catalog(&target_mergeinfo_cat_wc,
639 &inherited,
640 include_descendants,
641 inherit,
642 local_abspath,
643 NULL, NULL,
644 ignore_invalid_mergeinfo,
645 ctx,
646 result_pool,
647 scratch_pool));
648 if (inherited_p)
649 *inherited_p = inherited;
650
651 /* If we want LOCAL_ABSPATH's inherited mergeinfo, were we able to
652 get it from the working copy? If not, then we must ask the
653 repository. */
654 if (! (inherited
655 || (inherit == svn_mergeinfo_explicit)
656 || (repos_relpath
657 && target_mergeinfo_cat_wc
658 && svn_hash_gets(target_mergeinfo_cat_wc, repos_relpath))))
659 {
660 repos_only = TRUE;
661 /* We already have any subtree mergeinfo from the working copy, no
662 need to ask the server for it again. */
663 include_descendants = FALSE;
664 }
665 }
666
667 if (repos_only)
668 {
669 /* No need to check the repos if this is a local addition. */
670 if (url != NULL)
671 {
672 apr_hash_t *original_props;
673
674 /* Check to see if we have local modifications which removed all of
675 TARGET_WCPATH's pristine mergeinfo. If that is the case then
676 TARGET_WCPATH effectively has no mergeinfo. */
677 SVN_ERR(svn_wc_get_pristine_props(&original_props,
678 ctx->wc_ctx, local_abspath,
679 result_pool, scratch_pool));
680 if (!svn_hash_gets(original_props, SVN_PROP_MERGEINFO))
681 {
682 apr_pool_t *sesspool = NULL;
683
684 if (! ra_session)
685 {
686 sesspool = svn_pool_create(scratch_pool);
687 SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
688 ctx,
689 sesspool, sesspool));
690 }
691
692 SVN_ERR(svn_client__get_repos_mergeinfo_catalog(
693 &target_mergeinfo_cat_repos, ra_session,
694 url, target_rev, inherit,
695 TRUE, include_descendants,
696 result_pool, scratch_pool));
697
698 if (target_mergeinfo_cat_repos
699 && svn_hash_gets(target_mergeinfo_cat_repos, repos_relpath))
700 {
701 if (inherited_p)
702 *inherited_p = TRUE;
703 if (from_repos)
704 *from_repos = TRUE;
705 }
706
707 /* If we created an RA_SESSION above, destroy it.
708 Otherwise, if reparented an existing session, point
709 it back where it was when we were called. */
710 if (sesspool)
711 {
712 svn_pool_destroy(sesspool);
713 }
714 }
715 }
716 }
717
718 /* Combine the mergeinfo from the working copy and repository as needed. */
719 if (target_mergeinfo_cat_wc)
720 {
721 *target_mergeinfo_catalog = target_mergeinfo_cat_wc;
722 if (target_mergeinfo_cat_repos)
723 SVN_ERR(svn_mergeinfo_catalog_merge(*target_mergeinfo_catalog,
724 target_mergeinfo_cat_repos,
725 result_pool, scratch_pool));
726 }
727 else if (target_mergeinfo_cat_repos)
728 {
729 *target_mergeinfo_catalog = target_mergeinfo_cat_repos;
730 }
731 else
732 {
733 *target_mergeinfo_catalog = NULL;
734 }
735
736 return SVN_NO_ERROR;
737 }
738
739
740 svn_error_t *
svn_client__get_history_as_mergeinfo(svn_mergeinfo_t * mergeinfo_p,svn_boolean_t * has_rev_zero_history,const svn_client__pathrev_t * pathrev,svn_revnum_t range_youngest,svn_revnum_t range_oldest,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * pool)741 svn_client__get_history_as_mergeinfo(svn_mergeinfo_t *mergeinfo_p,
742 svn_boolean_t *has_rev_zero_history,
743 const svn_client__pathrev_t *pathrev,
744 svn_revnum_t range_youngest,
745 svn_revnum_t range_oldest,
746 svn_ra_session_t *ra_session,
747 svn_client_ctx_t *ctx,
748 apr_pool_t *pool)
749 {
750 apr_array_header_t *segments;
751
752 /* Fetch the location segments for our URL@PEG_REVNUM. */
753 if (! SVN_IS_VALID_REVNUM(range_youngest))
754 range_youngest = pathrev->rev;
755 if (! SVN_IS_VALID_REVNUM(range_oldest))
756 range_oldest = 0;
757
758 SVN_ERR(svn_client__repos_location_segments(&segments, ra_session,
759 pathrev->url, pathrev->rev,
760 range_youngest, range_oldest,
761 ctx, pool));
762
763 if (has_rev_zero_history)
764 {
765 *has_rev_zero_history = FALSE;
766 if (segments->nelts)
767 {
768 svn_location_segment_t *oldest_segment =
769 APR_ARRAY_IDX(segments, 0, svn_location_segment_t *);
770 if (oldest_segment->range_start == 0)
771 *has_rev_zero_history = TRUE;
772 }
773 }
774
775 SVN_ERR(svn_mergeinfo__mergeinfo_from_segments(mergeinfo_p, segments, pool));
776
777 return SVN_NO_ERROR;
778 }
779
780
781 /*-----------------------------------------------------------------------*/
782
783 /*** Eliding mergeinfo. ***/
784
785 /* Given the mergeinfo (CHILD_MERGEINFO) for a path, and the
786 mergeinfo of its nearest ancestor with mergeinfo (PARENT_MERGEINFO), compare
787 CHILD_MERGEINFO to PARENT_MERGEINFO to see if the former elides to
788 the latter, following the elision rules described in
789 svn_client__elide_mergeinfo()'s docstring. Set *ELIDES to whether
790 or not CHILD_MERGEINFO is redundant.
791
792 Note: This function assumes that PARENT_MERGEINFO is definitive;
793 i.e. if it is NULL then the caller not only walked the entire WC
794 looking for inherited mergeinfo, but queried the repository if none
795 was found in the WC. This is rather important since this function
796 says empty mergeinfo should be elided if PARENT_MERGEINFO is NULL,
797 and we don't want to do that unless we are *certain* that the empty
798 mergeinfo on PATH isn't overriding anything.
799
800 If PATH_SUFFIX and PARENT_MERGEINFO are not NULL append PATH_SUFFIX
801 to each path in PARENT_MERGEINFO before performing the comparison. */
802 static svn_error_t *
should_elide_mergeinfo(svn_boolean_t * elides,svn_mergeinfo_t parent_mergeinfo,svn_mergeinfo_t child_mergeinfo,const char * path_suffix,apr_pool_t * scratch_pool)803 should_elide_mergeinfo(svn_boolean_t *elides,
804 svn_mergeinfo_t parent_mergeinfo,
805 svn_mergeinfo_t child_mergeinfo,
806 const char *path_suffix,
807 apr_pool_t *scratch_pool)
808 {
809 /* Easy out: No child mergeinfo to elide. */
810 if (child_mergeinfo == NULL)
811 {
812 *elides = FALSE;
813 }
814 else if (apr_hash_count(child_mergeinfo) == 0)
815 {
816 /* Empty mergeinfo elides to empty mergeinfo or to "nothing",
817 i.e. it isn't overriding any parent. Otherwise it doesn't
818 elide. */
819 *elides = (!parent_mergeinfo || apr_hash_count(parent_mergeinfo) == 0);
820 }
821 else if (!parent_mergeinfo || apr_hash_count(parent_mergeinfo) == 0)
822 {
823 /* Non-empty mergeinfo never elides to empty mergeinfo
824 or no mergeinfo. */
825 *elides = FALSE;
826 }
827 else
828 {
829 /* Both CHILD_MERGEINFO and PARENT_MERGEINFO are non-NULL and
830 non-empty. */
831 svn_mergeinfo_t path_tweaked_parent_mergeinfo;
832
833 /* If we need to adjust the paths in PARENT_MERGEINFO do it now. */
834 if (path_suffix)
835 SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
836 &path_tweaked_parent_mergeinfo, parent_mergeinfo,
837 path_suffix, scratch_pool, scratch_pool));
838 else
839 path_tweaked_parent_mergeinfo = parent_mergeinfo;
840
841 SVN_ERR(svn_mergeinfo__equals(elides,
842 path_tweaked_parent_mergeinfo,
843 child_mergeinfo, TRUE, scratch_pool));
844 }
845
846 return SVN_NO_ERROR;
847 }
848
849 /* Helper for svn_client__elide_mergeinfo().
850
851 Given a working copy LOCAL_ABSPATH, its mergeinfo hash CHILD_MERGEINFO, and
852 the mergeinfo of LOCAL_ABSPATH's nearest ancestor PARENT_MERGEINFO, use
853 should_elide_mergeinfo() to decide whether or not CHILD_MERGEINFO elides to
854 PARENT_MERGEINFO; PATH_SUFFIX means the same as in that function.
855
856 If elision does occur, then remove the mergeinfo for LOCAL_ABSPATH.
857
858 If CHILD_MERGEINFO is NULL, do nothing.
859
860 Use SCRATCH_POOL for temporary allocations.
861 */
862 static svn_error_t *
elide_mergeinfo(svn_mergeinfo_t parent_mergeinfo,svn_mergeinfo_t child_mergeinfo,const char * local_abspath,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)863 elide_mergeinfo(svn_mergeinfo_t parent_mergeinfo,
864 svn_mergeinfo_t child_mergeinfo,
865 const char *local_abspath,
866 svn_client_ctx_t *ctx,
867 apr_pool_t *scratch_pool)
868 {
869 svn_boolean_t elides;
870
871 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
872
873 SVN_ERR(should_elide_mergeinfo(&elides,
874 parent_mergeinfo, child_mergeinfo, NULL,
875 scratch_pool));
876
877 if (elides)
878 {
879 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, SVN_PROP_MERGEINFO,
880 NULL, svn_depth_empty, TRUE, NULL,
881 NULL, NULL /* cancellation */,
882 NULL, NULL /* notification */,
883 scratch_pool));
884
885 if (ctx->notify_func2)
886 {
887 svn_wc_notify_t *notify;
888
889 notify = svn_wc_create_notify(local_abspath,
890 svn_wc_notify_merge_elide_info,
891 scratch_pool);
892 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
893
894 notify = svn_wc_create_notify(local_abspath,
895 svn_wc_notify_update_update,
896 scratch_pool);
897 notify->prop_state = svn_wc_notify_state_changed;
898
899 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
900 }
901 }
902
903 return SVN_NO_ERROR;
904 }
905
906
907 svn_error_t *
svn_client__elide_mergeinfo(const char * target_abspath,const char * wc_elision_limit_abspath,svn_client_ctx_t * ctx,apr_pool_t * pool)908 svn_client__elide_mergeinfo(const char *target_abspath,
909 const char *wc_elision_limit_abspath,
910 svn_client_ctx_t *ctx,
911 apr_pool_t *pool)
912 {
913 const char *limit_abspath = wc_elision_limit_abspath;
914
915 SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
916 SVN_ERR_ASSERT(!wc_elision_limit_abspath || svn_dirent_is_absolute(wc_elision_limit_abspath));
917
918 /* Check for first easy out: We are already at the limit path. */
919 if (!limit_abspath
920 || strcmp(target_abspath, limit_abspath) != 0)
921 {
922 svn_mergeinfo_t target_mergeinfo;
923 svn_mergeinfo_t mergeinfo = NULL;
924 svn_boolean_t inherited;
925 const char *walk_path;
926 svn_error_t *err;
927
928 /* Get the TARGET_WCPATH's explicit mergeinfo. */
929 err = svn_client__get_wc_mergeinfo(&target_mergeinfo, &inherited,
930 svn_mergeinfo_inherited,
931 target_abspath,
932 limit_abspath,
933 &walk_path, FALSE,
934 ctx, pool, pool);
935 if (err)
936 {
937 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
938 {
939 /* Issue #3896: If we attempt elision because invalid
940 mergeinfo is present on TARGET_WCPATH, then don't let
941 the merge fail, just skip the elision attempt. */
942 svn_error_clear(err);
943 return SVN_NO_ERROR;
944 }
945 else
946 {
947 return svn_error_trace(err);
948 }
949 }
950
951 /* If TARGET_WCPATH has no explicit mergeinfo, there's nothing to
952 elide, we're done. */
953 if (inherited || target_mergeinfo == NULL)
954 return SVN_NO_ERROR;
955
956 /* Get TARGET_WCPATH's inherited mergeinfo from the WC. */
957 err = svn_client__get_wc_mergeinfo(&mergeinfo, NULL,
958 svn_mergeinfo_nearest_ancestor,
959 target_abspath,
960 limit_abspath,
961 &walk_path, FALSE, ctx, pool, pool);
962 if (err)
963 {
964 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
965 {
966 /* Issue #3896 again, but invalid mergeinfo is inherited. */
967 svn_error_clear(err);
968 return SVN_NO_ERROR;
969 }
970 else
971 {
972 return svn_error_trace(err);
973 }
974 }
975
976 /* If TARGET_WCPATH inherited no mergeinfo from the WC and we are
977 not limiting our search to the working copy then check if it
978 inherits any from the repos. */
979 if (!mergeinfo && !wc_elision_limit_abspath)
980 {
981 err = svn_client__get_wc_or_repos_mergeinfo(
982 &mergeinfo, NULL, NULL, TRUE,
983 svn_mergeinfo_nearest_ancestor,
984 NULL, target_abspath, ctx, pool);
985 if (err)
986 {
987 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
988 {
989 /* Issue #3896 again, but invalid mergeinfo is inherited
990 from the repository. */
991 svn_error_clear(err);
992 return SVN_NO_ERROR;
993 }
994 else
995 {
996 return svn_error_trace(err);
997 }
998 }
999 }
1000
1001 /* If there is nowhere to elide TARGET_WCPATH's mergeinfo to and
1002 the elision is limited, then we are done.*/
1003 if (!mergeinfo && wc_elision_limit_abspath)
1004 return SVN_NO_ERROR;
1005
1006 SVN_ERR(elide_mergeinfo(mergeinfo, target_mergeinfo, target_abspath,
1007 ctx, pool));
1008 }
1009 return SVN_NO_ERROR;
1010 }
1011
1012
1013 /* Set *MERGEINFO_CATALOG to the explicit or inherited mergeinfo for
1014 PATH_OR_URL@PEG_REVISION. If INCLUDE_DESCENDANTS is true, also
1015 store in *MERGEINFO_CATALOG the explicit mergeinfo on any subtrees
1016 under PATH_OR_URL. Key all mergeinfo in *MERGEINFO_CATALOG on
1017 repository relpaths.
1018
1019 If no mergeinfo is found then set *MERGEINFO_CATALOG to NULL.
1020
1021 Set *REPOS_ROOT to the root URL of the repository associated with
1022 PATH_OR_URL.
1023
1024 If RA_SESSION is NOT NULL and PATH_OR_URL refers to a URL, RA_SESSION
1025 (which must be of the repository containing PATH_OR_URL) will be used
1026 instead of a temporary RA session. Caller is responsible for reparenting
1027 the session if it wants to use it after the call.
1028
1029 Allocate *MERGEINFO_CATALOG and all its contents in RESULT_POOL. Use
1030 SCRATCH_POOL for all temporary allocations.
1031
1032 Return SVN_ERR_UNSUPPORTED_FEATURE if the server does not support
1033 Merge Tracking. */
1034 static svn_error_t *
get_mergeinfo(svn_mergeinfo_catalog_t * mergeinfo_catalog,const char ** repos_root,const char * path_or_url,const svn_opt_revision_t * peg_revision,svn_boolean_t include_descendants,svn_boolean_t ignore_invalid_mergeinfo,svn_client_ctx_t * ctx,svn_ra_session_t * ra_session,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1035 get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo_catalog,
1036 const char **repos_root,
1037 const char *path_or_url,
1038 const svn_opt_revision_t *peg_revision,
1039 svn_boolean_t include_descendants,
1040 svn_boolean_t ignore_invalid_mergeinfo,
1041 svn_client_ctx_t *ctx,
1042 svn_ra_session_t *ra_session,
1043 apr_pool_t *result_pool,
1044 apr_pool_t *scratch_pool)
1045 {
1046 const char *local_abspath;
1047 svn_boolean_t use_url = svn_path_is_url(path_or_url);
1048 svn_client__pathrev_t *peg_loc;
1049
1050 if (ra_session && svn_path_is_url(path_or_url))
1051 {
1052 SVN_ERR(svn_ra_reparent(ra_session, path_or_url, scratch_pool));
1053 SVN_ERR(svn_client__resolve_rev_and_url(&peg_loc, ra_session,
1054 path_or_url,
1055 peg_revision,
1056 peg_revision,
1057 ctx, scratch_pool));
1058 }
1059 else
1060 {
1061 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &peg_loc,
1062 path_or_url, NULL,
1063 peg_revision,
1064 peg_revision, ctx, scratch_pool));
1065 }
1066
1067 /* If PATH_OR_URL is as working copy path determine if we will need to
1068 contact the repository for the requested PEG_REVISION. */
1069 if (!use_url)
1070 {
1071 svn_client__pathrev_t *origin;
1072 SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
1073 scratch_pool));
1074
1075 SVN_ERR(svn_client__wc_node_get_origin(&origin, local_abspath, ctx,
1076 scratch_pool, scratch_pool));
1077 if (!origin
1078 || strcmp(origin->url, peg_loc->url) != 0
1079 || peg_loc->rev != origin->rev)
1080 {
1081 use_url = TRUE; /* Don't rely on local mergeinfo */
1082 }
1083 }
1084
1085 SVN_ERR(svn_ra_get_repos_root2(ra_session, repos_root, result_pool));
1086
1087 if (use_url)
1088 {
1089 SVN_ERR(svn_client__get_repos_mergeinfo_catalog(
1090 mergeinfo_catalog, ra_session, peg_loc->url, peg_loc->rev,
1091 svn_mergeinfo_inherited, FALSE, include_descendants,
1092 result_pool, scratch_pool));
1093 }
1094 else /* ! svn_path_is_url() */
1095 {
1096 SVN_ERR(svn_client__get_wc_or_repos_mergeinfo_catalog(
1097 mergeinfo_catalog, NULL, NULL, include_descendants, FALSE,
1098 ignore_invalid_mergeinfo, svn_mergeinfo_inherited,
1099 ra_session, path_or_url, ctx,
1100 result_pool, scratch_pool));
1101 }
1102
1103 return SVN_NO_ERROR;
1104 }
1105
1106 /*** In-memory mergeinfo elision ***/
1107 svn_error_t *
svn_client__elide_mergeinfo_catalog(svn_mergeinfo_catalog_t mergeinfo_catalog,apr_pool_t * scratch_pool)1108 svn_client__elide_mergeinfo_catalog(svn_mergeinfo_catalog_t mergeinfo_catalog,
1109 apr_pool_t *scratch_pool)
1110 {
1111 apr_array_header_t *sorted_hash;
1112 apr_array_header_t *elidable_paths = apr_array_make(scratch_pool, 1,
1113 sizeof(const char *));
1114 apr_array_header_t *dir_stack = apr_array_make(scratch_pool, 1,
1115 sizeof(const char *));
1116 apr_pool_t *iterpool;
1117 int i;
1118
1119 /* Here's the general algorithm:
1120 Walk through the paths sorted in tree order. For each path, pop
1121 the dir_stack until it is either empty or the top item contains a parent
1122 of the current path. Check to see if that mergeinfo is then elidable,
1123 and build the list of elidable mergeinfo based upon that determination.
1124 Finally, push the path of interest onto the stack, and continue. */
1125 sorted_hash = svn_sort__hash(mergeinfo_catalog,
1126 svn_sort_compare_items_as_paths,
1127 scratch_pool);
1128 iterpool = svn_pool_create(scratch_pool);
1129 for (i = 0; i < sorted_hash->nelts; i++)
1130 {
1131 svn_sort__item_t *item = &APR_ARRAY_IDX(sorted_hash, i,
1132 svn_sort__item_t);
1133 const char *path = item->key;
1134
1135 if (dir_stack->nelts > 0)
1136 {
1137 const char *top;
1138 const char *path_suffix;
1139 svn_boolean_t elides = FALSE;
1140
1141 svn_pool_clear(iterpool);
1142
1143 /* Pop off any paths which are not ancestors of PATH. */
1144 do
1145 {
1146 top = APR_ARRAY_IDX(dir_stack, dir_stack->nelts - 1,
1147 const char *);
1148 path_suffix = svn_dirent_is_child(top, path, NULL);
1149
1150 if (!path_suffix)
1151 apr_array_pop(dir_stack);
1152 }
1153 while (dir_stack->nelts > 0 && !path_suffix);
1154
1155 /* If we have a path suffix, it means we haven't popped the stack
1156 clean. */
1157 if (path_suffix)
1158 {
1159 SVN_ERR(should_elide_mergeinfo(&elides,
1160 svn_hash_gets(mergeinfo_catalog, top),
1161 svn_hash_gets(mergeinfo_catalog, path),
1162 path_suffix,
1163 iterpool));
1164
1165 if (elides)
1166 APR_ARRAY_PUSH(elidable_paths, const char *) = path;
1167 }
1168 }
1169
1170 APR_ARRAY_PUSH(dir_stack, const char *) = path;
1171 }
1172 svn_pool_destroy(iterpool);
1173
1174 /* Now remove the elidable paths from the catalog. */
1175 for (i = 0; i < elidable_paths->nelts; i++)
1176 {
1177 const char *path = APR_ARRAY_IDX(elidable_paths, i, const char *);
1178 svn_hash_sets(mergeinfo_catalog, path, NULL);
1179 }
1180
1181 return SVN_NO_ERROR;
1182 }
1183
1184
1185 /* Helper for filter_log_entry_with_rangelist().
1186
1187 DEPTH_FIRST_CATALOG_INDEX is an array of svn_sort__item_t's. The keys are
1188 repository-absolute const char *paths, the values are svn_mergeinfo_t for
1189 each path.
1190
1191 Return a pointer to the mergeinfo value of the nearest path-wise ancestor
1192 of FSPATH in DEPTH_FIRST_CATALOG_INDEX. A path is considered its
1193 own ancestor, so if a key exactly matches FSPATH, return that
1194 key's mergeinfo and set *ANCESTOR_IS_SELF to true (set it to false in all
1195 other cases).
1196
1197 If DEPTH_FIRST_CATALOG_INDEX is NULL, empty, or no ancestor is found, then
1198 return NULL. */
1199 static svn_mergeinfo_t
find_nearest_ancestor(const apr_array_header_t * depth_first_catalog_index,svn_boolean_t * ancestor_is_self,const char * fspath)1200 find_nearest_ancestor(const apr_array_header_t *depth_first_catalog_index,
1201 svn_boolean_t *ancestor_is_self,
1202 const char *fspath)
1203 {
1204 int ancestor_index = -1;
1205
1206 *ancestor_is_self = FALSE;
1207
1208 if (depth_first_catalog_index)
1209 {
1210 int i;
1211
1212 for (i = 0; i < depth_first_catalog_index->nelts; i++)
1213 {
1214 svn_sort__item_t item = APR_ARRAY_IDX(depth_first_catalog_index, i,
1215 svn_sort__item_t);
1216 if (svn_fspath__skip_ancestor(item.key, fspath))
1217 {
1218 ancestor_index = i;
1219
1220 /* There's no nearer ancestor than FSPATH itself. */
1221 if (strcmp(item.key, fspath) == 0)
1222 {
1223 *ancestor_is_self = TRUE;
1224 break;
1225 }
1226 }
1227
1228 }
1229 }
1230
1231 if (ancestor_index == -1)
1232 return NULL;
1233 else
1234 return (APR_ARRAY_IDX(depth_first_catalog_index,
1235 ancestor_index,
1236 svn_sort__item_t)).value;
1237 }
1238
1239 /* Baton for use with the filter_log_entry_with_rangelist()
1240 svn_log_entry_receiver_t callback. */
1241 struct filter_log_entry_baton_t
1242 {
1243 /* Is TRUE if RANGELIST describes potentially merged revisions, is FALSE
1244 if RANGELIST describes potentially eligible revisions. */
1245 svn_boolean_t filtering_merged;
1246
1247 /* Unsorted array of repository relative paths representing the merge
1248 sources. There will be more than one source */
1249 const apr_array_header_t *merge_source_fspaths;
1250
1251 /* The repository-absolute path we are calling svn_client_log5() on. */
1252 const char *target_fspath;
1253
1254 /* Mergeinfo catalog for the tree rooted at TARGET_FSPATH.
1255 The path keys must be repository-absolute. */
1256 svn_mergeinfo_catalog_t target_mergeinfo_catalog;
1257
1258 /* Depth first sorted array of svn_sort__item_t's for
1259 TARGET_MERGEINFO_CATALOG. */
1260 apr_array_header_t *depth_first_catalog_index;
1261
1262 /* A rangelist describing all the revisions potentially merged or
1263 potentially eligible for merging (see FILTERING_MERGED) based on
1264 the target's explicit or inherited mergeinfo. */
1265 const svn_rangelist_t *rangelist;
1266
1267 /* The wrapped svn_log_entry_receiver_t callback and baton which
1268 filter_log_entry_with_rangelist() is acting as a filter for. */
1269 svn_log_entry_receiver_t log_receiver;
1270 void *log_receiver_baton;
1271
1272 svn_client_ctx_t *ctx;
1273 };
1274
1275 /* Implements the svn_log_entry_receiver_t interface. BATON is a
1276 `struct filter_log_entry_baton_t *'.
1277
1278 Call the wrapped log receiver BATON->log_receiver (with
1279 BATON->log_receiver_baton) if:
1280
1281 BATON->FILTERING_MERGED is FALSE and the changes represented by LOG_ENTRY
1282 have been fully merged from BATON->merge_source_fspaths to the WC target
1283 based on the mergeinfo for the WC contained in BATON->TARGET_MERGEINFO_CATALOG.
1284
1285 Or
1286
1287 BATON->FILTERING_MERGED is TRUE and the changes represented by LOG_ENTRY
1288 have not been merged, or only partially merged, from
1289 BATON->merge_source_fspaths to the WC target based on the mergeinfo for the
1290 WC contained in BATON->TARGET_MERGEINFO_CATALOG. */
1291 static svn_error_t *
filter_log_entry_with_rangelist(void * baton,svn_log_entry_t * log_entry,apr_pool_t * pool)1292 filter_log_entry_with_rangelist(void *baton,
1293 svn_log_entry_t *log_entry,
1294 apr_pool_t *pool)
1295 {
1296 struct filter_log_entry_baton_t *fleb = baton;
1297 svn_rangelist_t *intersection, *this_rangelist;
1298
1299 if (fleb->ctx->cancel_func)
1300 SVN_ERR(fleb->ctx->cancel_func(fleb->ctx->cancel_baton));
1301
1302 /* Ignore r0 because there can be no "change 0" in a merge range. */
1303 if (log_entry->revision == 0)
1304 return SVN_NO_ERROR;
1305
1306 this_rangelist = svn_rangelist__initialize(log_entry->revision - 1,
1307 log_entry->revision,
1308 TRUE, pool);
1309
1310 /* Don't consider inheritance yet, see if LOG_ENTRY->REVISION is
1311 fully or partially represented in BATON->RANGELIST. */
1312 SVN_ERR(svn_rangelist_intersect(&intersection, fleb->rangelist,
1313 this_rangelist, FALSE, pool));
1314 if (! (intersection && intersection->nelts))
1315 return SVN_NO_ERROR;
1316
1317 SVN_ERR_ASSERT(intersection->nelts == 1);
1318
1319 /* Ok, we know LOG_ENTRY->REVISION is represented in BATON->RANGELIST,
1320 but is it only partially represented, i.e. is the corresponding range in
1321 BATON->RANGELIST non-inheritable? Ask for the same intersection as
1322 above but consider inheritance this time, if the intersection is empty
1323 we know the range in BATON->RANGELIST is non-inheritable. */
1324 SVN_ERR(svn_rangelist_intersect(&intersection, fleb->rangelist,
1325 this_rangelist, TRUE, pool));
1326 log_entry->non_inheritable = !intersection->nelts;
1327
1328 /* If the paths changed by LOG_ENTRY->REVISION are provided we can determine
1329 if LOG_ENTRY->REVISION, while only partially represented in
1330 BATON->RANGELIST, is in fact completely applied to all affected paths.
1331 ### And ... what if it is, or if it isn't? What do we do with the answer?
1332 And how do we cope if the changed paths are not provided? */
1333 if ((log_entry->non_inheritable || !fleb->filtering_merged)
1334 && log_entry->changed_paths2)
1335 {
1336 apr_hash_index_t *hi;
1337 svn_boolean_t all_subtrees_have_this_rev = TRUE;
1338 svn_rangelist_t *this_rev_rangelist =
1339 svn_rangelist__initialize(log_entry->revision - 1,
1340 log_entry->revision, TRUE, pool);
1341 apr_pool_t *iterpool = svn_pool_create(pool);
1342
1343 for (hi = apr_hash_first(pool, log_entry->changed_paths2);
1344 hi;
1345 hi = apr_hash_next(hi))
1346 {
1347 int i;
1348 const char *path = svn__apr_hash_index_key(hi);
1349 svn_log_changed_path2_t *change = svn__apr_hash_index_val(hi);
1350 const char *target_fspath_affected;
1351 svn_mergeinfo_t nearest_ancestor_mergeinfo;
1352 svn_boolean_t found_this_revision = FALSE;
1353 const char *merge_source_rel_target;
1354 const char *merge_source_fspath;
1355 svn_boolean_t ancestor_is_self;
1356
1357 svn_pool_clear(iterpool);
1358
1359 /* Check that PATH is a subtree of at least one of the
1360 merge sources. If not then ignore this path. */
1361 for (i = 0; i < fleb->merge_source_fspaths->nelts; i++)
1362 {
1363 merge_source_fspath = APR_ARRAY_IDX(fleb->merge_source_fspaths,
1364 i, const char *);
1365
1366 merge_source_rel_target
1367 = svn_fspath__skip_ancestor(merge_source_fspath, path);
1368 if (merge_source_rel_target)
1369 {
1370 /* If MERGE_SOURCE was itself deleted, replaced, or added
1371 in LOG_ENTRY->REVISION then ignore this PATH since you
1372 can't merge a addition or deletion of yourself. */
1373 if (merge_source_rel_target[0] == '\0'
1374 && (change->action != 'M'))
1375 i = fleb->merge_source_fspaths->nelts;
1376 break;
1377 }
1378 }
1379 /* If we examined every merge source path and PATH is a child of
1380 none of them then we can ignore this PATH. */
1381 if (i == fleb->merge_source_fspaths->nelts)
1382 continue;
1383
1384 /* Calculate the target path which PATH would affect if merged. */
1385 target_fspath_affected = svn_fspath__join(fleb->target_fspath,
1386 merge_source_rel_target,
1387 iterpool);
1388
1389 nearest_ancestor_mergeinfo =
1390 find_nearest_ancestor(fleb->depth_first_catalog_index,
1391 &ancestor_is_self,
1392 target_fspath_affected);
1393
1394 /* Issue #3791: A path should never have explicit mergeinfo
1395 describing its own addition (that's self-referential). Nor will
1396 it have explicit mergeinfo describing its own deletion (we
1397 obviously can't add new mergeinfo to a path we are deleting).
1398
1399 This lack of explicit mergeinfo should not cause such revisions
1400 to show up as eligible however. If PATH was deleted, replaced,
1401 or added in LOG_ENTRY->REVISION, but the corresponding
1402 TARGET_PATH_AFFECTED already exists and has explicit mergeinfo
1403 describing merges from PATH *after* LOG_ENTRY->REVISION, then
1404 ignore this PATH. If it was deleted in LOG_ENTRY->REVISION it's
1405 obviously back. If it was added or replaced it's still around
1406 possibly it was replaced one or more times, but it's back now.
1407 Regardless, LOG_ENTRY->REVISION is *not* an eligible revision! */
1408 if (nearest_ancestor_mergeinfo &&
1409 ancestor_is_self /* Explicit mergeinfo on TARGET_PATH_AFFECTED */
1410 && (change->action != 'M'))
1411 {
1412 svn_rangelist_t *rangelist =
1413 svn_hash_gets(nearest_ancestor_mergeinfo, path);
1414 if (rangelist)
1415 {
1416 svn_merge_range_t *youngest_range = APR_ARRAY_IDX(
1417 rangelist, rangelist->nelts - 1, svn_merge_range_t *);
1418
1419 if (youngest_range
1420 && (youngest_range->end > log_entry->revision))
1421 continue;
1422 }
1423 }
1424
1425 if (nearest_ancestor_mergeinfo)
1426 {
1427 apr_hash_index_t *hi2;
1428
1429 for (hi2 = apr_hash_first(iterpool, nearest_ancestor_mergeinfo);
1430 hi2;
1431 hi2 = apr_hash_next(hi2))
1432 {
1433 const char *mergeinfo_path = svn__apr_hash_index_key(hi2);
1434 svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi2);
1435
1436 /* Does the mergeinfo for PATH reflect if
1437 LOG_ENTRY->REVISION was previously merged
1438 from MERGE_SOURCE_FSPATH? */
1439 if (svn_fspath__skip_ancestor(merge_source_fspath,
1440 mergeinfo_path))
1441 {
1442 /* Something was merged from MERGE_SOURCE_FSPATH, does
1443 it include LOG_ENTRY->REVISION? */
1444 SVN_ERR(svn_rangelist_intersect(&intersection,
1445 rangelist,
1446 this_rev_rangelist,
1447 FALSE,
1448 iterpool));
1449 if (intersection->nelts)
1450 {
1451 if (ancestor_is_self)
1452 {
1453 /* TARGET_PATH_AFFECTED has explicit mergeinfo,
1454 so we don't need to worry if that mergeinfo
1455 is inheritable or not. */
1456 found_this_revision = TRUE;
1457 break;
1458 }
1459 else
1460 {
1461 /* TARGET_PATH_AFFECTED inherited its mergeinfo,
1462 so we have to ignore non-inheritable
1463 ranges. */
1464 SVN_ERR(svn_rangelist_intersect(
1465 &intersection,
1466 rangelist,
1467 this_rev_rangelist,
1468 TRUE, iterpool));
1469 if (intersection->nelts)
1470 {
1471 found_this_revision = TRUE;
1472 break;
1473 }
1474 }
1475 }
1476 }
1477 }
1478 }
1479
1480 if (!found_this_revision)
1481 {
1482 /* As soon as any PATH is found that is not fully merged for
1483 LOG_ENTRY->REVISION then we can stop. */
1484 all_subtrees_have_this_rev = FALSE;
1485 break;
1486 }
1487 }
1488
1489 svn_pool_destroy(iterpool);
1490
1491 if (all_subtrees_have_this_rev)
1492 {
1493 if (fleb->filtering_merged)
1494 log_entry->non_inheritable = FALSE;
1495 else
1496 return SVN_NO_ERROR;
1497 }
1498 }
1499
1500 /* Call the wrapped log receiver which this function is filtering for. */
1501 return fleb->log_receiver(fleb->log_receiver_baton, log_entry, pool);
1502 }
1503
1504 static svn_error_t *
logs_for_mergeinfo_rangelist(const char * source_url,const apr_array_header_t * merge_source_fspaths,svn_boolean_t filtering_merged,const svn_rangelist_t * rangelist,svn_boolean_t oldest_revs_first,svn_mergeinfo_catalog_t target_mergeinfo_catalog,const char * target_fspath,svn_boolean_t discover_changed_paths,const apr_array_header_t * revprops,svn_log_entry_receiver_t log_receiver,void * log_receiver_baton,svn_client_ctx_t * ctx,svn_ra_session_t * ra_session,apr_pool_t * scratch_pool)1505 logs_for_mergeinfo_rangelist(const char *source_url,
1506 const apr_array_header_t *merge_source_fspaths,
1507 svn_boolean_t filtering_merged,
1508 const svn_rangelist_t *rangelist,
1509 svn_boolean_t oldest_revs_first,
1510 svn_mergeinfo_catalog_t target_mergeinfo_catalog,
1511 const char *target_fspath,
1512 svn_boolean_t discover_changed_paths,
1513 const apr_array_header_t *revprops,
1514 svn_log_entry_receiver_t log_receiver,
1515 void *log_receiver_baton,
1516 svn_client_ctx_t *ctx,
1517 svn_ra_session_t *ra_session,
1518 apr_pool_t *scratch_pool)
1519 {
1520 svn_merge_range_t *oldest_range, *youngest_range;
1521 svn_revnum_t oldest_rev, youngest_rev;
1522 struct filter_log_entry_baton_t fleb;
1523
1524 if (! rangelist->nelts)
1525 return SVN_NO_ERROR;
1526
1527 /* Calculate and construct the bounds of our log request. */
1528 youngest_range = APR_ARRAY_IDX(rangelist, rangelist->nelts - 1,
1529 svn_merge_range_t *);
1530 youngest_rev = youngest_range->end;
1531 oldest_range = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *);
1532 oldest_rev = oldest_range->start;
1533
1534 if (! target_mergeinfo_catalog)
1535 target_mergeinfo_catalog = apr_hash_make(scratch_pool);
1536
1537 /* FILTER_LOG_ENTRY_BATON_T->TARGET_MERGEINFO_CATALOG's keys are required
1538 to be repository-absolute. */
1539 SVN_ERR(svn_mergeinfo__add_prefix_to_catalog(&target_mergeinfo_catalog,
1540 target_mergeinfo_catalog, "/",
1541 scratch_pool, scratch_pool));
1542
1543 /* Build the log filtering callback baton. */
1544 fleb.filtering_merged = filtering_merged;
1545 fleb.merge_source_fspaths = merge_source_fspaths;
1546 fleb.target_mergeinfo_catalog = target_mergeinfo_catalog;
1547 fleb.depth_first_catalog_index =
1548 svn_sort__hash(target_mergeinfo_catalog,
1549 svn_sort_compare_items_as_paths,
1550 scratch_pool);
1551 fleb.target_fspath = target_fspath;
1552 fleb.rangelist = rangelist;
1553 fleb.log_receiver = log_receiver;
1554 fleb.log_receiver_baton = log_receiver_baton;
1555 fleb.ctx = ctx;
1556
1557 if (!ra_session)
1558 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, source_url,
1559 NULL, NULL, FALSE, FALSE, ctx,
1560 scratch_pool, scratch_pool));
1561 else
1562 SVN_ERR(svn_ra_reparent(ra_session, source_url, scratch_pool));
1563
1564 {
1565 apr_array_header_t *target;
1566 target = apr_array_make(scratch_pool, 1, sizeof(const char *));
1567 APR_ARRAY_PUSH(target, const char *) = "";
1568
1569 SVN_ERR(svn_ra_get_log2(ra_session, target,
1570 oldest_revs_first ? oldest_rev : youngest_rev,
1571 oldest_revs_first ? youngest_rev : oldest_rev,
1572 0 /* limit */,
1573 discover_changed_paths,
1574 FALSE /* strict_node_history */,
1575 FALSE /* include_merged_revisions */,
1576 revprops,
1577 filter_log_entry_with_rangelist, &fleb,
1578 scratch_pool));
1579 }
1580
1581 /* Check for cancellation. */
1582 if (ctx->cancel_func)
1583 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1584
1585 return SVN_NO_ERROR;
1586 }
1587
1588 /* Set *OUT_MERGEINFO to a shallow copy of MERGEINFO with each source path
1589 converted to a (URI-encoded) URL based on REPOS_ROOT_URL. *OUT_MERGEINFO
1590 is declared as 'apr_hash_t *' because its key do not obey the rules of
1591 'svn_mergeinfo_t'.
1592
1593 Allocate *OUT_MERGEINFO and the new keys in RESULT_POOL. Use
1594 SCRATCH_POOL for any temporary allocations. */
1595 static svn_error_t *
mergeinfo_relpaths_to_urls(apr_hash_t ** out_mergeinfo,svn_mergeinfo_t mergeinfo,const char * repos_root_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1596 mergeinfo_relpaths_to_urls(apr_hash_t **out_mergeinfo,
1597 svn_mergeinfo_t mergeinfo,
1598 const char *repos_root_url,
1599 apr_pool_t *result_pool,
1600 apr_pool_t *scratch_pool)
1601 {
1602 *out_mergeinfo = NULL;
1603 if (mergeinfo)
1604 {
1605 apr_hash_index_t *hi;
1606 apr_hash_t *full_path_mergeinfo = apr_hash_make(result_pool);
1607
1608 for (hi = apr_hash_first(scratch_pool, mergeinfo);
1609 hi; hi = apr_hash_next(hi))
1610 {
1611 const char *key = svn__apr_hash_index_key(hi);
1612 void *val = svn__apr_hash_index_val(hi);
1613
1614 svn_hash_sets(full_path_mergeinfo,
1615 svn_path_url_add_component2(repos_root_url, key + 1,
1616 result_pool),
1617 val);
1618 }
1619 *out_mergeinfo = full_path_mergeinfo;
1620 }
1621
1622 return SVN_NO_ERROR;
1623 }
1624
1625
1626 /*** Public APIs ***/
1627
1628 svn_error_t *
svn_client_mergeinfo_get_merged(apr_hash_t ** mergeinfo_p,const char * path_or_url,const svn_opt_revision_t * peg_revision,svn_client_ctx_t * ctx,apr_pool_t * pool)1629 svn_client_mergeinfo_get_merged(apr_hash_t **mergeinfo_p,
1630 const char *path_or_url,
1631 const svn_opt_revision_t *peg_revision,
1632 svn_client_ctx_t *ctx,
1633 apr_pool_t *pool)
1634 {
1635 const char *repos_root;
1636 svn_mergeinfo_catalog_t mergeinfo_cat;
1637 svn_mergeinfo_t mergeinfo;
1638
1639 SVN_ERR(get_mergeinfo(&mergeinfo_cat, &repos_root, path_or_url,
1640 peg_revision, FALSE, FALSE, ctx, NULL, pool, pool));
1641 if (mergeinfo_cat)
1642 {
1643 const char *repos_relpath;
1644
1645 if (! svn_path_is_url(path_or_url))
1646 {
1647 SVN_ERR(svn_dirent_get_absolute(&path_or_url, path_or_url, pool));
1648 SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, NULL, NULL,
1649 ctx->wc_ctx, path_or_url,
1650 pool, pool));
1651 }
1652 else
1653 {
1654 repos_relpath = svn_uri_skip_ancestor(repos_root, path_or_url, pool);
1655
1656 SVN_ERR_ASSERT(repos_relpath != NULL); /* Or get_mergeinfo failed */
1657 }
1658
1659 mergeinfo = svn_hash_gets(mergeinfo_cat, repos_relpath);
1660 }
1661 else
1662 {
1663 mergeinfo = NULL;
1664 }
1665
1666 SVN_ERR(mergeinfo_relpaths_to_urls(mergeinfo_p, mergeinfo,
1667 repos_root, pool, pool));
1668 return SVN_NO_ERROR;
1669 }
1670
1671 svn_error_t *
svn_client__mergeinfo_log(svn_boolean_t finding_merged,const char * target_path_or_url,const svn_opt_revision_t * target_peg_revision,svn_mergeinfo_catalog_t * target_mergeinfo_catalog,const char * source_path_or_url,const svn_opt_revision_t * source_peg_revision,const svn_opt_revision_t * source_start_revision,const svn_opt_revision_t * source_end_revision,svn_log_entry_receiver_t log_receiver,void * log_receiver_baton,svn_boolean_t discover_changed_paths,svn_depth_t depth,const apr_array_header_t * revprops,svn_client_ctx_t * ctx,svn_ra_session_t * ra_session,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1672 svn_client__mergeinfo_log(svn_boolean_t finding_merged,
1673 const char *target_path_or_url,
1674 const svn_opt_revision_t *target_peg_revision,
1675 svn_mergeinfo_catalog_t *target_mergeinfo_catalog,
1676 const char *source_path_or_url,
1677 const svn_opt_revision_t *source_peg_revision,
1678 const svn_opt_revision_t *source_start_revision,
1679 const svn_opt_revision_t *source_end_revision,
1680 svn_log_entry_receiver_t log_receiver,
1681 void *log_receiver_baton,
1682 svn_boolean_t discover_changed_paths,
1683 svn_depth_t depth,
1684 const apr_array_header_t *revprops,
1685 svn_client_ctx_t *ctx,
1686 svn_ra_session_t *ra_session,
1687 apr_pool_t *result_pool,
1688 apr_pool_t *scratch_pool)
1689 {
1690 const char *log_target = NULL;
1691 const char *repos_root;
1692 const char *target_repos_relpath;
1693 svn_mergeinfo_catalog_t target_mergeinfo_cat;
1694 svn_ra_session_t *target_session = NULL;
1695 svn_client__pathrev_t *pathrev;
1696
1697 /* A hash of paths, at or under TARGET_PATH_OR_URL, mapped to
1698 rangelists. Not technically mergeinfo, so not using the
1699 svn_mergeinfo_t type. */
1700 apr_hash_t *inheritable_subtree_merges;
1701
1702 svn_mergeinfo_t source_history;
1703 svn_mergeinfo_t target_history;
1704 svn_rangelist_t *master_noninheritable_rangelist;
1705 svn_rangelist_t *master_inheritable_rangelist;
1706 apr_array_header_t *merge_source_fspaths =
1707 apr_array_make(scratch_pool, 1, sizeof(const char *));
1708 apr_hash_index_t *hi_catalog;
1709 apr_hash_index_t *hi;
1710 apr_pool_t *iterpool;
1711 svn_boolean_t oldest_revs_first = TRUE;
1712 apr_pool_t *subpool;
1713
1714 /* We currently only support depth = empty | infinity. */
1715 if (depth != svn_depth_infinity && depth != svn_depth_empty)
1716 return svn_error_create(
1717 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1718 _("Only depths 'infinity' and 'empty' are currently supported"));
1719
1720 /* Validate and sanitize the incoming source operative revision range. */
1721 if (!((source_start_revision->kind == svn_opt_revision_unspecified) ||
1722 (source_start_revision->kind == svn_opt_revision_number) ||
1723 (source_start_revision->kind == svn_opt_revision_date) ||
1724 (source_start_revision->kind == svn_opt_revision_head)))
1725 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
1726 if (!((source_end_revision->kind == svn_opt_revision_unspecified) ||
1727 (source_end_revision->kind == svn_opt_revision_number) ||
1728 (source_end_revision->kind == svn_opt_revision_date) ||
1729 (source_end_revision->kind == svn_opt_revision_head)))
1730 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
1731 if ((source_end_revision->kind != svn_opt_revision_unspecified)
1732 && (source_start_revision->kind == svn_opt_revision_unspecified))
1733 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
1734 if ((source_end_revision->kind == svn_opt_revision_unspecified)
1735 && (source_start_revision->kind != svn_opt_revision_unspecified))
1736 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
1737
1738 subpool = svn_pool_create(scratch_pool);
1739
1740 if (ra_session)
1741 target_session = ra_session;
1742
1743 /* We need the union of TARGET_PATH_OR_URL@TARGET_PEG_REVISION's mergeinfo
1744 and MERGE_SOURCE_URL's history. It's not enough to do path
1745 matching, because renames in the history of MERGE_SOURCE_URL
1746 throw that all in a tizzy. Of course, if there's no mergeinfo on
1747 the target, that vastly simplifies matters (we'll have nothing to
1748 do). */
1749 /* This get_mergeinfo() call doubles as a mergeinfo capabilities check. */
1750 if (target_mergeinfo_catalog)
1751 {
1752 if (*target_mergeinfo_catalog)
1753 {
1754 /* The caller provided the mergeinfo catalog for
1755 TARGET_PATH_OR_URL, so we don't need to accquire
1756 it ourselves. We do need to get the repos_root
1757 though, because get_mergeinfo() won't do it for us. */
1758 target_mergeinfo_cat = *target_mergeinfo_catalog;
1759
1760 if (ra_session && svn_path_is_url(target_path_or_url))
1761 {
1762 SVN_ERR(svn_ra_reparent(ra_session, target_path_or_url, subpool));
1763 SVN_ERR(svn_client__resolve_rev_and_url(&pathrev, ra_session,
1764 target_path_or_url,
1765 target_peg_revision,
1766 target_peg_revision,
1767 ctx, subpool));
1768 target_session = ra_session;
1769 }
1770 else
1771 {
1772 SVN_ERR(svn_client__ra_session_from_path2(&target_session,
1773 &pathrev,
1774 target_path_or_url,
1775 NULL,
1776 target_peg_revision,
1777 target_peg_revision,
1778 ctx, subpool));
1779 }
1780 SVN_ERR(svn_ra_get_repos_root2(target_session, &repos_root,
1781 scratch_pool));
1782 }
1783 else
1784 {
1785 /* The caller didn't provide the mergeinfo catalog for
1786 TARGET_PATH_OR_URL, but wants us to pass a copy back
1787 when we get it, so use RESULT_POOL. */
1788 SVN_ERR(get_mergeinfo(target_mergeinfo_catalog, &repos_root,
1789 target_path_or_url, target_peg_revision,
1790 depth == svn_depth_infinity, TRUE,
1791 ctx, ra_session, result_pool, scratch_pool));
1792 target_mergeinfo_cat = *target_mergeinfo_catalog;
1793 }
1794 }
1795 else
1796 {
1797 /* The caller didn't provide the mergeinfo catalog for
1798 TARGET_PATH_OR_URL, nor does it want a copy, so we can use
1799 nothing but SCRATCH_POOL. */
1800 SVN_ERR(get_mergeinfo(&target_mergeinfo_cat, &repos_root,
1801 target_path_or_url, target_peg_revision,
1802 depth == svn_depth_infinity, TRUE,
1803 ctx, ra_session, scratch_pool, scratch_pool));
1804 }
1805
1806 if (!svn_path_is_url(target_path_or_url))
1807 {
1808 SVN_ERR(svn_dirent_get_absolute(&target_path_or_url,
1809 target_path_or_url, scratch_pool));
1810 SVN_ERR(svn_wc__node_get_repos_info(NULL, &target_repos_relpath,
1811 NULL, NULL,
1812 ctx->wc_ctx, target_path_or_url,
1813 scratch_pool, scratch_pool));
1814 }
1815 else
1816 {
1817 target_repos_relpath = svn_uri_skip_ancestor(repos_root,
1818 target_path_or_url,
1819 scratch_pool);
1820
1821 /* TARGET_REPOS_REL should be non-NULL, else get_mergeinfo
1822 should have failed. */
1823 SVN_ERR_ASSERT(target_repos_relpath != NULL);
1824 }
1825
1826 if (!target_mergeinfo_cat)
1827 {
1828 /* If we are looking for what has been merged and there is no
1829 mergeinfo then we already know the answer. If we are looking
1830 for eligible revisions then create a catalog with empty mergeinfo
1831 on the target. This is semantically equivalent to no mergeinfo
1832 and gives us something to combine with MERGE_SOURCE_URL's
1833 history. */
1834 if (finding_merged)
1835 {
1836 svn_pool_destroy(subpool);
1837 return SVN_NO_ERROR;
1838 }
1839 else
1840 {
1841 target_mergeinfo_cat = apr_hash_make(scratch_pool);
1842 svn_hash_sets(target_mergeinfo_cat, target_repos_relpath,
1843 apr_hash_make(scratch_pool));
1844 }
1845 }
1846
1847 /* Fetch the location history as mergeinfo, for the source branch
1848 * (between the given start and end revisions), and, if we're finding
1849 * merged revisions, then also for the entire target branch.
1850 *
1851 * ### TODO: As the source and target must be in the same repository, we
1852 * should share a single session, tracking the two URLs separately. */
1853 {
1854 svn_ra_session_t *source_session;
1855 svn_revnum_t start_rev, end_rev, youngest_rev = SVN_INVALID_REVNUM;
1856
1857 if (! finding_merged)
1858 {
1859 if (!target_session)
1860 SVN_ERR(svn_client__ra_session_from_path2(&target_session, &pathrev,
1861 target_path_or_url, NULL,
1862 target_peg_revision,
1863 target_peg_revision,
1864 ctx, subpool));
1865 SVN_ERR(svn_client__get_history_as_mergeinfo(&target_history, NULL,
1866 pathrev,
1867 SVN_INVALID_REVNUM,
1868 SVN_INVALID_REVNUM,
1869 target_session, ctx,
1870 scratch_pool));
1871 }
1872
1873 if (target_session
1874 && svn_path_is_url(source_path_or_url)
1875 && repos_root
1876 && svn_uri_skip_ancestor(repos_root, source_path_or_url, subpool))
1877 {
1878 /* We can re-use the existing session */
1879 source_session = target_session;
1880 SVN_ERR(svn_ra_reparent(source_session, source_path_or_url, subpool));
1881 SVN_ERR(svn_client__resolve_rev_and_url(&pathrev, source_session,
1882 source_path_or_url,
1883 source_peg_revision,
1884 source_peg_revision,
1885 ctx, subpool));
1886 }
1887 else
1888 {
1889 SVN_ERR(svn_client__ra_session_from_path2(&source_session, &pathrev,
1890 source_path_or_url, NULL,
1891 source_peg_revision,
1892 source_peg_revision,
1893 ctx, subpool));
1894 }
1895 SVN_ERR(svn_client__get_revision_number(&start_rev, &youngest_rev,
1896 ctx->wc_ctx, source_path_or_url,
1897 source_session,
1898 source_start_revision,
1899 subpool));
1900 SVN_ERR(svn_client__get_revision_number(&end_rev, &youngest_rev,
1901 ctx->wc_ctx, source_path_or_url,
1902 source_session,
1903 source_end_revision,
1904 subpool));
1905 SVN_ERR(svn_client__get_history_as_mergeinfo(&source_history, NULL,
1906 pathrev,
1907 MAX(end_rev, start_rev),
1908 MIN(end_rev, start_rev),
1909 source_session, ctx,
1910 scratch_pool));
1911 if (start_rev > end_rev)
1912 oldest_revs_first = FALSE;
1913 }
1914
1915 /* Separate the explicit or inherited mergeinfo on TARGET_PATH_OR_URL,
1916 and possibly its explicit subtree mergeinfo, into their
1917 inheritable and non-inheritable parts. */
1918 master_noninheritable_rangelist = apr_array_make(scratch_pool, 64,
1919 sizeof(svn_merge_range_t *));
1920 master_inheritable_rangelist = apr_array_make(scratch_pool, 64,
1921 sizeof(svn_merge_range_t *));
1922 inheritable_subtree_merges = apr_hash_make(scratch_pool);
1923
1924 iterpool = svn_pool_create(scratch_pool);
1925
1926 for (hi_catalog = apr_hash_first(scratch_pool, target_mergeinfo_cat);
1927 hi_catalog;
1928 hi_catalog = apr_hash_next(hi_catalog))
1929 {
1930 svn_mergeinfo_t subtree_mergeinfo = svn__apr_hash_index_val(hi_catalog);
1931 svn_mergeinfo_t subtree_history;
1932 svn_mergeinfo_t subtree_source_history;
1933 svn_mergeinfo_t subtree_inheritable_mergeinfo;
1934 svn_mergeinfo_t subtree_noninheritable_mergeinfo;
1935 svn_mergeinfo_t merged_noninheritable;
1936 svn_mergeinfo_t merged;
1937 const char *subtree_path = svn__apr_hash_index_key(hi_catalog);
1938 svn_boolean_t is_subtree = strcmp(subtree_path,
1939 target_repos_relpath) != 0;
1940 svn_pool_clear(iterpool);
1941
1942 if (is_subtree)
1943 {
1944 /* If SUBTREE_PATH is a proper subtree of TARGET_PATH_OR_URL
1945 then make a copy of SOURCE_HISTORY that is path adjusted
1946 for the subtree. */
1947 const char *subtree_rel_path =
1948 subtree_path + strlen(target_repos_relpath) + 1;
1949
1950 SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
1951 &subtree_source_history, source_history,
1952 subtree_rel_path, scratch_pool, scratch_pool));
1953
1954 if (!finding_merged)
1955 SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
1956 &subtree_history, target_history,
1957 subtree_rel_path, scratch_pool, scratch_pool));
1958 }
1959 else
1960 {
1961 subtree_source_history = source_history;
1962 if (!finding_merged)
1963 subtree_history = target_history;
1964 }
1965
1966 if (!finding_merged)
1967 {
1968 svn_mergeinfo_t merged_via_history;
1969 SVN_ERR(svn_mergeinfo_intersect2(&merged_via_history,
1970 subtree_history,
1971 subtree_source_history, TRUE,
1972 scratch_pool, iterpool));
1973 SVN_ERR(svn_mergeinfo_merge2(subtree_mergeinfo,
1974 merged_via_history,
1975 scratch_pool, scratch_pool));
1976 }
1977
1978 SVN_ERR(svn_mergeinfo_inheritable2(&subtree_inheritable_mergeinfo,
1979 subtree_mergeinfo, NULL,
1980 SVN_INVALID_REVNUM,
1981 SVN_INVALID_REVNUM,
1982 TRUE, scratch_pool, iterpool));
1983 SVN_ERR(svn_mergeinfo_inheritable2(&subtree_noninheritable_mergeinfo,
1984 subtree_mergeinfo, NULL,
1985 SVN_INVALID_REVNUM,
1986 SVN_INVALID_REVNUM,
1987 FALSE, scratch_pool, iterpool));
1988
1989 /* Find the intersection of the non-inheritable part of
1990 SUBTREE_MERGEINFO and SOURCE_HISTORY. svn_mergeinfo_intersect2()
1991 won't consider non-inheritable and inheritable ranges
1992 intersecting unless we ignore inheritance, but in doing so the
1993 resulting intersections have all inheritable ranges. To get
1994 around this we set the inheritance on the result to all
1995 non-inheritable. */
1996 SVN_ERR(svn_mergeinfo_intersect2(&merged_noninheritable,
1997 subtree_noninheritable_mergeinfo,
1998 subtree_source_history, FALSE,
1999 scratch_pool, iterpool));
2000 svn_mergeinfo__set_inheritance(merged_noninheritable, FALSE,
2001 scratch_pool);
2002
2003 /* Keep track of all ranges partially merged to any and all
2004 subtrees. */
2005 SVN_ERR(svn_rangelist__merge_many(master_noninheritable_rangelist,
2006 merged_noninheritable,
2007 scratch_pool, iterpool));
2008
2009 /* Find the intersection of the inheritable part of TGT_MERGEINFO
2010 and SOURCE_HISTORY. */
2011 SVN_ERR(svn_mergeinfo_intersect2(&merged,
2012 subtree_inheritable_mergeinfo,
2013 subtree_source_history, FALSE,
2014 scratch_pool, iterpool));
2015
2016 /* Keep track of all ranges fully merged to any and all
2017 subtrees. */
2018 if (apr_hash_count(merged))
2019 {
2020 /* The inheritable rangelist merged from SUBTREE_SOURCE_HISTORY
2021 to SUBTREE_PATH. */
2022 svn_rangelist_t *subtree_merged_rangelist =
2023 apr_array_make(scratch_pool, 1, sizeof(svn_merge_range_t *));
2024
2025 SVN_ERR(svn_rangelist__merge_many(master_inheritable_rangelist,
2026 merged, scratch_pool, iterpool));
2027 SVN_ERR(svn_rangelist__merge_many(subtree_merged_rangelist,
2028 merged, scratch_pool, iterpool));
2029
2030 svn_hash_sets(inheritable_subtree_merges, subtree_path,
2031 subtree_merged_rangelist);
2032 }
2033 else
2034 {
2035 /* Map SUBTREE_PATH to an empty rangelist if there was nothing
2036 fully merged. e.g. Only empty or non-inheritable mergeinfo
2037 on the subtree or mergeinfo unrelated to the source. */
2038 svn_hash_sets(inheritable_subtree_merges, subtree_path,
2039 apr_array_make(scratch_pool, 0,
2040 sizeof(svn_merge_range_t *)));
2041 }
2042 }
2043
2044 /* Make sure every range in MASTER_INHERITABLE_RANGELIST is fully merged to
2045 each subtree (including the target itself). Any revisions which don't
2046 exist in *every* subtree are *potentially* only partially merged to the
2047 tree rooted at TARGET_PATH_OR_URL, so move those revisions to
2048 MASTER_NONINHERITABLE_RANGELIST. It may turn out that that a revision
2049 was merged to the only subtree it affects, but we need to examine the
2050 logs to make this determination (which will be done by
2051 logs_for_mergeinfo_rangelist). */
2052 if (master_inheritable_rangelist->nelts)
2053 {
2054 for (hi = apr_hash_first(scratch_pool, inheritable_subtree_merges);
2055 hi;
2056 hi = apr_hash_next(hi))
2057 {
2058 svn_rangelist_t *deleted_rangelist;
2059 svn_rangelist_t *added_rangelist;
2060 svn_rangelist_t *subtree_merged_rangelist =
2061 svn__apr_hash_index_val(hi);
2062
2063 svn_pool_clear(iterpool);
2064
2065 SVN_ERR(svn_rangelist_diff(&deleted_rangelist, &added_rangelist,
2066 master_inheritable_rangelist,
2067 subtree_merged_rangelist, TRUE,
2068 iterpool));
2069
2070 if (deleted_rangelist->nelts)
2071 {
2072 svn_rangelist__set_inheritance(deleted_rangelist, FALSE);
2073 SVN_ERR(svn_rangelist_merge2(master_noninheritable_rangelist,
2074 deleted_rangelist,
2075 scratch_pool, iterpool));
2076 SVN_ERR(svn_rangelist_remove(&master_inheritable_rangelist,
2077 deleted_rangelist,
2078 master_inheritable_rangelist,
2079 FALSE,
2080 scratch_pool));
2081 }
2082 }
2083 }
2084
2085 if (finding_merged)
2086 {
2087 /* Roll all the merged revisions into one rangelist. */
2088 SVN_ERR(svn_rangelist_merge2(master_inheritable_rangelist,
2089 master_noninheritable_rangelist,
2090 scratch_pool, scratch_pool));
2091
2092 }
2093 else
2094 {
2095 /* Create the starting rangelist for what might be eligible. */
2096 svn_rangelist_t *source_master_rangelist =
2097 apr_array_make(scratch_pool, 1, sizeof(svn_merge_range_t *));
2098
2099 SVN_ERR(svn_rangelist__merge_many(source_master_rangelist,
2100 source_history,
2101 scratch_pool, scratch_pool));
2102
2103 /* From what might be eligible subtract what we know is
2104 partially merged and then merge that back. */
2105 SVN_ERR(svn_rangelist_remove(&source_master_rangelist,
2106 master_noninheritable_rangelist,
2107 source_master_rangelist,
2108 FALSE, scratch_pool));
2109 SVN_ERR(svn_rangelist_merge2(source_master_rangelist,
2110 master_noninheritable_rangelist,
2111 scratch_pool, scratch_pool));
2112 SVN_ERR(svn_rangelist_remove(&master_inheritable_rangelist,
2113 master_inheritable_rangelist,
2114 source_master_rangelist,
2115 TRUE, scratch_pool));
2116 }
2117
2118 /* Nothing merged? Not even when considering shared history if
2119 looking for eligible revisions (i.e. !FINDING_MERGED)? Then there
2120 is nothing more to do. */
2121 if (! master_inheritable_rangelist->nelts)
2122 {
2123 svn_pool_destroy(iterpool);
2124 return SVN_NO_ERROR;
2125 }
2126 else
2127 {
2128 /* Determine the correct (youngest) target for 'svn log'. */
2129 svn_merge_range_t *youngest_range
2130 = APR_ARRAY_IDX(master_inheritable_rangelist,
2131 master_inheritable_rangelist->nelts - 1,
2132 svn_merge_range_t *);
2133 svn_rangelist_t *youngest_rangelist =
2134 svn_rangelist__initialize(youngest_range->end - 1,
2135 youngest_range->end,
2136 youngest_range->inheritable,
2137 scratch_pool);;
2138
2139 for (hi = apr_hash_first(scratch_pool, source_history);
2140 hi;
2141 hi = apr_hash_next(hi))
2142 {
2143 const char *key = svn__apr_hash_index_key(hi);
2144 svn_rangelist_t *subtree_merged_rangelist =
2145 svn__apr_hash_index_val(hi);
2146 svn_rangelist_t *intersecting_rangelist;
2147
2148 svn_pool_clear(iterpool);
2149 SVN_ERR(svn_rangelist_intersect(&intersecting_rangelist,
2150 youngest_rangelist,
2151 subtree_merged_rangelist,
2152 FALSE, iterpool));
2153
2154 APR_ARRAY_PUSH(merge_source_fspaths, const char *) = key;
2155
2156 if (intersecting_rangelist->nelts)
2157 log_target = key;
2158 }
2159 }
2160
2161 svn_pool_destroy(iterpool);
2162
2163 /* Step 4: Finally, we run 'svn log' to drive our log receiver, but
2164 using a receiver filter to only allow revisions to pass through
2165 that are in our rangelist. */
2166 log_target = svn_path_url_add_component2(repos_root, log_target + 1,
2167 scratch_pool);
2168
2169 {
2170 svn_error_t *err;
2171
2172 err = logs_for_mergeinfo_rangelist(log_target, merge_source_fspaths,
2173 finding_merged,
2174 master_inheritable_rangelist,
2175 oldest_revs_first,
2176 target_mergeinfo_cat,
2177 svn_fspath__join("/",
2178 target_repos_relpath,
2179 scratch_pool),
2180 discover_changed_paths,
2181 revprops,
2182 log_receiver, log_receiver_baton,
2183 ctx, target_session, scratch_pool);
2184
2185 /* Close the source and target sessions. */
2186 svn_pool_destroy(subpool); /* For SVN_ERR_CEASE_INVOCATION */
2187
2188 return svn_error_trace(err);
2189 }
2190 }
2191
2192 svn_error_t *
svn_client_mergeinfo_log2(svn_boolean_t finding_merged,const char * target_path_or_url,const svn_opt_revision_t * target_peg_revision,const char * source_path_or_url,const svn_opt_revision_t * source_peg_revision,const svn_opt_revision_t * source_start_revision,const svn_opt_revision_t * source_end_revision,svn_log_entry_receiver_t log_receiver,void * log_receiver_baton,svn_boolean_t discover_changed_paths,svn_depth_t depth,const apr_array_header_t * revprops,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)2193 svn_client_mergeinfo_log2(svn_boolean_t finding_merged,
2194 const char *target_path_or_url,
2195 const svn_opt_revision_t *target_peg_revision,
2196 const char *source_path_or_url,
2197 const svn_opt_revision_t *source_peg_revision,
2198 const svn_opt_revision_t *source_start_revision,
2199 const svn_opt_revision_t *source_end_revision,
2200 svn_log_entry_receiver_t log_receiver,
2201 void *log_receiver_baton,
2202 svn_boolean_t discover_changed_paths,
2203 svn_depth_t depth,
2204 const apr_array_header_t *revprops,
2205 svn_client_ctx_t *ctx,
2206 apr_pool_t *scratch_pool)
2207 {
2208 return svn_error_trace(
2209 svn_client__mergeinfo_log(finding_merged, target_path_or_url,
2210 target_peg_revision, NULL,
2211 source_path_or_url, source_peg_revision,
2212 source_start_revision, source_end_revision,
2213 log_receiver, log_receiver_baton,
2214 discover_changed_paths, depth, revprops,
2215 ctx, NULL,
2216 scratch_pool, scratch_pool));
2217 }
2218
2219 svn_error_t *
svn_client_suggest_merge_sources(apr_array_header_t ** suggestions,const char * path_or_url,const svn_opt_revision_t * peg_revision,svn_client_ctx_t * ctx,apr_pool_t * pool)2220 svn_client_suggest_merge_sources(apr_array_header_t **suggestions,
2221 const char *path_or_url,
2222 const svn_opt_revision_t *peg_revision,
2223 svn_client_ctx_t *ctx,
2224 apr_pool_t *pool)
2225 {
2226 const char *repos_root;
2227 const char *copyfrom_path;
2228 apr_array_header_t *list;
2229 svn_revnum_t copyfrom_rev;
2230 svn_mergeinfo_catalog_t mergeinfo_cat;
2231 svn_mergeinfo_t mergeinfo;
2232 apr_hash_index_t *hi;
2233
2234 list = apr_array_make(pool, 1, sizeof(const char *));
2235
2236 /* In our ideal algorithm, the list of recommendations should be
2237 ordered by:
2238
2239 1. The most recent existing merge source.
2240 2. The copyfrom source (which will also be listed as a merge
2241 source if the copy was made with a 1.5+ client and server).
2242 3. All other merge sources, most recent to least recent.
2243
2244 However, determining the order of application of merge sources
2245 requires a new RA API. Until such an API is available, our
2246 algorithm will be:
2247
2248 1. The copyfrom source.
2249 2. All remaining merge sources (unordered).
2250 */
2251
2252 /* ### TODO: Share ra_session batons to improve efficiency? */
2253 SVN_ERR(get_mergeinfo(&mergeinfo_cat, &repos_root, path_or_url,
2254 peg_revision, FALSE, FALSE, ctx, NULL, pool, pool));
2255
2256 if (mergeinfo_cat && apr_hash_count(mergeinfo_cat))
2257 {
2258 /* We asked only for the PATH_OR_URL's mergeinfo, not any of its
2259 descendants. So if there is anything in the catalog it is the
2260 mergeinfo for PATH_OR_URL. */
2261 mergeinfo = svn__apr_hash_index_val(apr_hash_first(pool, mergeinfo_cat));
2262 }
2263 else
2264 {
2265 mergeinfo = NULL;
2266 }
2267
2268 SVN_ERR(svn_client__get_copy_source(©from_path, ©from_rev,
2269 path_or_url, peg_revision, ctx,
2270 pool, pool));
2271 if (copyfrom_path)
2272 {
2273 APR_ARRAY_PUSH(list, const char *) =
2274 svn_path_url_add_component2(repos_root, copyfrom_path, pool);
2275 }
2276
2277 if (mergeinfo)
2278 {
2279 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
2280 {
2281 const char *rel_path = svn__apr_hash_index_key(hi);
2282
2283 if (copyfrom_path == NULL || strcmp(rel_path, copyfrom_path) != 0)
2284 APR_ARRAY_PUSH(list, const char *) = \
2285 svn_path_url_add_component2(repos_root, rel_path + 1, pool);
2286 }
2287 }
2288
2289 *suggestions = list;
2290 return SVN_NO_ERROR;
2291 }
2292
2293 svn_error_t *
svn_client__mergeinfo_status(svn_boolean_t * mergeinfo_changes,svn_wc_context_t * wc_ctx,const char * local_abspath,apr_pool_t * scratch_pool)2294 svn_client__mergeinfo_status(svn_boolean_t *mergeinfo_changes,
2295 svn_wc_context_t *wc_ctx,
2296 const char *local_abspath,
2297 apr_pool_t *scratch_pool)
2298 {
2299 apr_array_header_t *propchanges;
2300 int i;
2301
2302 *mergeinfo_changes = FALSE;
2303
2304 SVN_ERR(svn_wc_get_prop_diffs2(&propchanges, NULL, wc_ctx,
2305 local_abspath, scratch_pool, scratch_pool));
2306
2307 for (i = 0; i < propchanges->nelts; i++)
2308 {
2309 svn_prop_t prop = APR_ARRAY_IDX(propchanges, i, svn_prop_t);
2310 if (strcmp(prop.name, SVN_PROP_MERGEINFO) == 0)
2311 {
2312 *mergeinfo_changes = TRUE;
2313 break;
2314 }
2315 }
2316
2317 return SVN_NO_ERROR;
2318 }
2319