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_client_private.h"
42 #include "private/svn_opt_private.h"
43 #include "private/svn_mergeinfo_private.h"
44 #include "private/svn_ra_private.h"
45 #include "private/svn_sorts_private.h"
46 #include "private/svn_wc_private.h"
47 #include "private/svn_fspath.h"
48 #include "client.h"
49 #include "mergeinfo.h"
50 #include "svn_private_config.h"
51
52
53
54 svn_client__merge_path_t *
svn_client__merge_path_dup(const svn_client__merge_path_t * old,apr_pool_t * pool)55 svn_client__merge_path_dup(const svn_client__merge_path_t *old,
56 apr_pool_t *pool)
57 {
58 svn_client__merge_path_t *new = apr_pmemdup(pool, old, sizeof(*old));
59
60 new->abspath = apr_pstrdup(pool, old->abspath);
61 if (new->remaining_ranges)
62 new->remaining_ranges = svn_rangelist_dup(old->remaining_ranges, pool);
63 if (new->pre_merge_mergeinfo)
64 new->pre_merge_mergeinfo = svn_mergeinfo_dup(old->pre_merge_mergeinfo,
65 pool);
66 if (new->implicit_mergeinfo)
67 new->implicit_mergeinfo = svn_mergeinfo_dup(old->implicit_mergeinfo,
68 pool);
69
70 return new;
71 }
72
73 svn_client__merge_path_t *
svn_client__merge_path_create(const char * abspath,apr_pool_t * pool)74 svn_client__merge_path_create(const char *abspath,
75 apr_pool_t *pool)
76 {
77 svn_client__merge_path_t *result = apr_pcalloc(pool, sizeof(*result));
78
79 result->abspath = apr_pstrdup(pool, abspath);
80 return result;
81 }
82
83 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)84 svn_client__parse_mergeinfo(svn_mergeinfo_t *mergeinfo,
85 svn_wc_context_t *wc_ctx,
86 const char *local_abspath,
87 apr_pool_t *result_pool,
88 apr_pool_t *scratch_pool)
89 {
90 const svn_string_t *propval;
91
92 *mergeinfo = NULL;
93
94 /* ### Use svn_wc_prop_get() would actually be sufficient for now.
95 ### DannyB thinks that later we'll need behavior more like
96 ### svn_client__get_prop_from_wc(). */
97 SVN_ERR(svn_wc_prop_get2(&propval, wc_ctx, local_abspath, SVN_PROP_MERGEINFO,
98 scratch_pool, scratch_pool));
99 if (propval)
100 SVN_ERR(svn_mergeinfo_parse(mergeinfo, propval->data, result_pool));
101
102 return SVN_NO_ERROR;
103 }
104
105 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)106 svn_client__record_wc_mergeinfo(const char *local_abspath,
107 svn_mergeinfo_t mergeinfo,
108 svn_boolean_t do_notification,
109 svn_client_ctx_t *ctx,
110 apr_pool_t *scratch_pool)
111 {
112 svn_string_t *mergeinfo_str = NULL;
113 svn_boolean_t mergeinfo_changes = FALSE;
114
115 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
116
117 /* Convert MERGEINFO (if any) into text for storage as a property value. */
118 if (mergeinfo)
119 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_str, mergeinfo, scratch_pool));
120
121 if (do_notification && ctx->notify_func2)
122 SVN_ERR(svn_client__mergeinfo_status(&mergeinfo_changes, ctx->wc_ctx,
123 local_abspath, scratch_pool));
124
125 /* Record the new mergeinfo in the WC. */
126 /* ### Later, we'll want behavior more analogous to
127 ### svn_client__get_prop_from_wc(). */
128 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, SVN_PROP_MERGEINFO,
129 mergeinfo_str, svn_depth_empty,
130 TRUE /* skip checks */, NULL,
131 NULL, NULL /* cancellation */,
132 NULL, NULL /* notification */,
133 scratch_pool));
134
135 if (do_notification && ctx->notify_func2)
136 {
137 svn_wc_notify_t *notify =
138 svn_wc_create_notify(local_abspath,
139 svn_wc_notify_merge_record_info,
140 scratch_pool);
141 if (mergeinfo_changes)
142 notify->prop_state = svn_wc_notify_state_merged;
143 else
144 notify->prop_state = svn_wc_notify_state_changed;
145
146 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
147 }
148
149 return SVN_NO_ERROR;
150 }
151
152 svn_error_t *
svn_client__record_wc_mergeinfo_catalog(apr_hash_t * result_catalog,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)153 svn_client__record_wc_mergeinfo_catalog(apr_hash_t *result_catalog,
154 svn_client_ctx_t *ctx,
155 apr_pool_t *scratch_pool)
156 {
157 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
158
159 if (apr_hash_count(result_catalog))
160 {
161 int i;
162 apr_array_header_t *sorted_cat =
163 svn_sort__hash(result_catalog, svn_sort_compare_items_as_paths,
164 scratch_pool);
165
166 /* Write the mergeinfo out in sorted order of the paths (presumably just
167 * so that the notifications are in a predictable, convenient order). */
168 for (i = 0; i < sorted_cat->nelts; i++)
169 {
170 svn_sort__item_t elt = APR_ARRAY_IDX(sorted_cat, i,
171 svn_sort__item_t);
172 svn_error_t *err;
173
174 svn_pool_clear(iterpool);
175 err = svn_client__record_wc_mergeinfo(elt.key, elt.value, TRUE,
176 ctx, iterpool);
177
178 if (err && err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
179 {
180 /* PATH isn't just missing, it's not even versioned as far
181 as this working copy knows. But it was included in
182 MERGES, which means that the server knows about it.
183 Likely we don't have access to the source due to authz
184 restrictions. For now just clear the error and
185 continue... */
186 svn_error_clear(err);
187 }
188 else
189 {
190 SVN_ERR(err);
191 }
192 }
193 }
194 svn_pool_destroy(iterpool);
195 return SVN_NO_ERROR;
196 }
197
198 /*-----------------------------------------------------------------------*/
199
200 /*** Retrieving mergeinfo. ***/
201
202 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)203 svn_client__get_wc_mergeinfo(svn_mergeinfo_t *mergeinfo,
204 svn_boolean_t *inherited_p,
205 svn_mergeinfo_inheritance_t inherit,
206 const char *local_abspath,
207 const char *limit_abspath,
208 const char **walked_path,
209 svn_boolean_t ignore_invalid_mergeinfo,
210 svn_client_ctx_t *ctx,
211 apr_pool_t *result_pool,
212 apr_pool_t *scratch_pool)
213 {
214 const char *walk_relpath = "";
215 svn_mergeinfo_t wc_mergeinfo;
216 svn_revnum_t base_revision;
217 apr_pool_t *iterpool;
218 svn_boolean_t inherited;
219
220 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
221 if (limit_abspath)
222 SVN_ERR_ASSERT(svn_dirent_is_absolute(limit_abspath));
223
224 SVN_ERR(svn_wc__node_get_base(NULL, &base_revision, NULL, NULL, NULL, NULL,
225 ctx->wc_ctx, local_abspath,
226 TRUE /* ignore_enoent */,
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 /* ignore_enoent */,
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, scratch_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 = apr_hash_this_key(hi);
433 svn_string_t *propval = apr_hash_this_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 apr_hash_this_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 apr_hash_this_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, 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_error_t *err;
925
926 /* Get the TARGET_WCPATH's explicit mergeinfo. */
927 err = svn_client__get_wc_mergeinfo(&target_mergeinfo, NULL,
928 svn_mergeinfo_explicit,
929 target_abspath,
930 NULL, NULL, FALSE,
931 ctx, pool, pool);
932 if (err)
933 {
934 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
935 {
936 /* Issue #3896: If we attempt elision because invalid
937 mergeinfo is present on TARGET_WCPATH, then don't let
938 the merge fail, just skip the elision attempt. */
939 svn_error_clear(err);
940 return SVN_NO_ERROR;
941 }
942 else
943 {
944 return svn_error_trace(err);
945 }
946 }
947
948 /* If TARGET_WCPATH has no explicit mergeinfo, there's nothing to
949 elide, we're done. */
950 if (target_mergeinfo == NULL)
951 return SVN_NO_ERROR;
952
953 /* Get TARGET_WCPATH's inherited mergeinfo from the WC. */
954 err = svn_client__get_wc_mergeinfo(&mergeinfo, NULL,
955 svn_mergeinfo_nearest_ancestor,
956 target_abspath,
957 limit_abspath,
958 NULL, FALSE, ctx, pool, pool);
959 if (err)
960 {
961 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
962 {
963 /* Issue #3896 again, but invalid mergeinfo is inherited. */
964 svn_error_clear(err);
965 return SVN_NO_ERROR;
966 }
967 else
968 {
969 return svn_error_trace(err);
970 }
971 }
972
973 /* If TARGET_WCPATH inherited no mergeinfo from the WC and we are
974 not limiting our search to the working copy then check if it
975 inherits any from the repos. */
976 if (!mergeinfo && !wc_elision_limit_abspath)
977 {
978 err = svn_client__get_wc_or_repos_mergeinfo(
979 &mergeinfo, NULL, NULL, TRUE,
980 svn_mergeinfo_nearest_ancestor,
981 NULL, target_abspath, ctx, pool);
982 if (err)
983 {
984 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
985 {
986 /* Issue #3896 again, but invalid mergeinfo is inherited
987 from the repository. */
988 svn_error_clear(err);
989 return SVN_NO_ERROR;
990 }
991 else
992 {
993 return svn_error_trace(err);
994 }
995 }
996 }
997
998 /* If there is nowhere to elide TARGET_WCPATH's mergeinfo to and
999 the elision is limited, then we are done.*/
1000 if (!mergeinfo && wc_elision_limit_abspath)
1001 return SVN_NO_ERROR;
1002
1003 SVN_ERR(elide_mergeinfo(mergeinfo, target_mergeinfo, target_abspath,
1004 ctx, pool));
1005 }
1006 return SVN_NO_ERROR;
1007 }
1008
1009
1010 /* Set *MERGEINFO_CATALOG to the explicit or inherited mergeinfo for
1011 PATH_OR_URL@PEG_REVISION. If INCLUDE_DESCENDANTS is true, also
1012 store in *MERGEINFO_CATALOG the explicit mergeinfo on any subtrees
1013 under PATH_OR_URL. Key all mergeinfo in *MERGEINFO_CATALOG on
1014 repository relpaths.
1015
1016 If no mergeinfo is found then set *MERGEINFO_CATALOG to NULL.
1017
1018 Set *REPOS_ROOT to the root URL of the repository associated with
1019 PATH_OR_URL.
1020
1021 If RA_SESSION is NOT NULL and PATH_OR_URL refers to a URL, RA_SESSION
1022 (which must be of the repository containing PATH_OR_URL) will be used
1023 instead of a temporary RA session. Caller is responsible for reparenting
1024 the session if it wants to use it after the call.
1025
1026 Allocate *MERGEINFO_CATALOG and all its contents in RESULT_POOL. Use
1027 SCRATCH_POOL for all temporary allocations.
1028
1029 Return SVN_ERR_UNSUPPORTED_FEATURE if the server does not support
1030 Merge Tracking. */
1031 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)1032 get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo_catalog,
1033 const char **repos_root,
1034 const char *path_or_url,
1035 const svn_opt_revision_t *peg_revision,
1036 svn_boolean_t include_descendants,
1037 svn_boolean_t ignore_invalid_mergeinfo,
1038 svn_client_ctx_t *ctx,
1039 svn_ra_session_t *ra_session,
1040 apr_pool_t *result_pool,
1041 apr_pool_t *scratch_pool)
1042 {
1043 const char *local_abspath;
1044 svn_boolean_t use_url = svn_path_is_url(path_or_url);
1045 svn_client__pathrev_t *peg_loc;
1046
1047 if (ra_session && svn_path_is_url(path_or_url))
1048 {
1049 SVN_ERR(svn_ra_reparent(ra_session, path_or_url, scratch_pool));
1050 SVN_ERR(svn_client__resolve_rev_and_url(&peg_loc, ra_session,
1051 path_or_url,
1052 peg_revision,
1053 peg_revision,
1054 ctx, scratch_pool));
1055 }
1056 else
1057 {
1058 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &peg_loc,
1059 path_or_url, NULL,
1060 peg_revision,
1061 peg_revision, ctx, scratch_pool));
1062 }
1063
1064 /* If PATH_OR_URL is as working copy path determine if we will need to
1065 contact the repository for the requested PEG_REVISION. */
1066 if (!use_url)
1067 {
1068 svn_client__pathrev_t *origin;
1069 SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
1070 scratch_pool));
1071
1072 SVN_ERR(svn_client__wc_node_get_origin(&origin, local_abspath, ctx,
1073 scratch_pool, scratch_pool));
1074 if (!origin
1075 || strcmp(origin->url, peg_loc->url) != 0
1076 || peg_loc->rev != origin->rev)
1077 {
1078 use_url = TRUE; /* Don't rely on local mergeinfo */
1079 }
1080 }
1081
1082 SVN_ERR(svn_ra_get_repos_root2(ra_session, repos_root, result_pool));
1083
1084 if (use_url)
1085 {
1086 SVN_ERR(svn_client__get_repos_mergeinfo_catalog(
1087 mergeinfo_catalog, ra_session, peg_loc->url, peg_loc->rev,
1088 svn_mergeinfo_inherited, FALSE, include_descendants,
1089 result_pool, scratch_pool));
1090 }
1091 else /* ! svn_path_is_url() */
1092 {
1093 SVN_ERR(svn_client__get_wc_or_repos_mergeinfo_catalog(
1094 mergeinfo_catalog, NULL, NULL, include_descendants, FALSE,
1095 ignore_invalid_mergeinfo, svn_mergeinfo_inherited,
1096 ra_session, path_or_url, ctx,
1097 result_pool, scratch_pool));
1098 }
1099
1100 return SVN_NO_ERROR;
1101 }
1102
1103 /*** In-memory mergeinfo elision ***/
1104 svn_error_t *
svn_client__elide_mergeinfo_catalog(svn_mergeinfo_catalog_t mergeinfo_catalog,apr_pool_t * scratch_pool)1105 svn_client__elide_mergeinfo_catalog(svn_mergeinfo_catalog_t mergeinfo_catalog,
1106 apr_pool_t *scratch_pool)
1107 {
1108 apr_array_header_t *sorted_hash;
1109 apr_array_header_t *elidable_paths = apr_array_make(scratch_pool, 1,
1110 sizeof(const char *));
1111 apr_array_header_t *dir_stack = apr_array_make(scratch_pool, 1,
1112 sizeof(const char *));
1113 apr_pool_t *iterpool;
1114 int i;
1115
1116 /* Here's the general algorithm:
1117 Walk through the paths sorted in tree order. For each path, pop
1118 the dir_stack until it is either empty or the top item contains a parent
1119 of the current path. Check to see if that mergeinfo is then elidable,
1120 and build the list of elidable mergeinfo based upon that determination.
1121 Finally, push the path of interest onto the stack, and continue. */
1122 sorted_hash = svn_sort__hash(mergeinfo_catalog,
1123 svn_sort_compare_items_as_paths,
1124 scratch_pool);
1125 iterpool = svn_pool_create(scratch_pool);
1126 for (i = 0; i < sorted_hash->nelts; i++)
1127 {
1128 svn_sort__item_t *item = &APR_ARRAY_IDX(sorted_hash, i,
1129 svn_sort__item_t);
1130 const char *path = item->key;
1131
1132 if (dir_stack->nelts > 0)
1133 {
1134 const char *top;
1135 const char *path_suffix;
1136 svn_boolean_t elides = FALSE;
1137
1138 svn_pool_clear(iterpool);
1139
1140 /* Pop off any paths which are not ancestors of PATH. */
1141 do
1142 {
1143 top = APR_ARRAY_IDX(dir_stack, dir_stack->nelts - 1,
1144 const char *);
1145 path_suffix = svn_dirent_is_child(top, path, NULL);
1146
1147 if (!path_suffix)
1148 apr_array_pop(dir_stack);
1149 }
1150 while (dir_stack->nelts > 0 && !path_suffix);
1151
1152 /* If we have a path suffix, it means we haven't popped the stack
1153 clean. */
1154 if (path_suffix)
1155 {
1156 SVN_ERR(should_elide_mergeinfo(&elides,
1157 svn_hash_gets(mergeinfo_catalog, top),
1158 svn_hash_gets(mergeinfo_catalog, path),
1159 path_suffix,
1160 iterpool));
1161
1162 if (elides)
1163 APR_ARRAY_PUSH(elidable_paths, const char *) = path;
1164 }
1165 }
1166
1167 APR_ARRAY_PUSH(dir_stack, const char *) = path;
1168 }
1169 svn_pool_destroy(iterpool);
1170
1171 /* Now remove the elidable paths from the catalog. */
1172 for (i = 0; i < elidable_paths->nelts; i++)
1173 {
1174 const char *path = APR_ARRAY_IDX(elidable_paths, i, const char *);
1175 svn_hash_sets(mergeinfo_catalog, path, NULL);
1176 }
1177
1178 return SVN_NO_ERROR;
1179 }
1180
1181
1182 /* Helper for filter_log_entry_with_rangelist().
1183
1184 DEPTH_FIRST_CATALOG_INDEX is an array of svn_sort__item_t's. The keys are
1185 repository-absolute const char *paths, the values are svn_mergeinfo_t for
1186 each path.
1187
1188 Return a pointer to the mergeinfo value of the nearest path-wise ancestor
1189 of FSPATH in DEPTH_FIRST_CATALOG_INDEX. A path is considered its
1190 own ancestor, so if a key exactly matches FSPATH, return that
1191 key's mergeinfo and set *ANCESTOR_IS_SELF to true (set it to false in all
1192 other cases).
1193
1194 If DEPTH_FIRST_CATALOG_INDEX is NULL, empty, or no ancestor is found, then
1195 return NULL. */
1196 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)1197 find_nearest_ancestor(const apr_array_header_t *depth_first_catalog_index,
1198 svn_boolean_t *ancestor_is_self,
1199 const char *fspath)
1200 {
1201 int ancestor_index = -1;
1202
1203 *ancestor_is_self = FALSE;
1204
1205 if (depth_first_catalog_index)
1206 {
1207 int i;
1208
1209 for (i = 0; i < depth_first_catalog_index->nelts; i++)
1210 {
1211 svn_sort__item_t item = APR_ARRAY_IDX(depth_first_catalog_index, i,
1212 svn_sort__item_t);
1213 if (svn_fspath__skip_ancestor(item.key, fspath))
1214 {
1215 ancestor_index = i;
1216
1217 /* There's no nearer ancestor than FSPATH itself. */
1218 if (strcmp(item.key, fspath) == 0)
1219 {
1220 *ancestor_is_self = TRUE;
1221 break;
1222 }
1223 }
1224
1225 }
1226 }
1227
1228 if (ancestor_index == -1)
1229 return NULL;
1230 else
1231 return (APR_ARRAY_IDX(depth_first_catalog_index,
1232 ancestor_index,
1233 svn_sort__item_t)).value;
1234 }
1235
1236 /* Baton for use with the filter_log_entry_with_rangelist()
1237 svn_log_entry_receiver_t callback. */
1238 struct filter_log_entry_baton_t
1239 {
1240 /* Is TRUE if RANGELIST describes potentially merged revisions, is FALSE
1241 if RANGELIST describes potentially eligible revisions. */
1242 svn_boolean_t filtering_merged;
1243
1244 /* Unsorted array of repository relative paths representing the merge
1245 sources. There will be more than one source */
1246 const apr_array_header_t *merge_source_fspaths;
1247
1248 /* The repository-absolute path we are calling svn_client_log5() on. */
1249 const char *target_fspath;
1250
1251 /* Mergeinfo catalog for the tree rooted at TARGET_FSPATH.
1252 The path keys must be repository-absolute. */
1253 svn_mergeinfo_catalog_t target_mergeinfo_catalog;
1254
1255 /* Depth first sorted array of svn_sort__item_t's for
1256 TARGET_MERGEINFO_CATALOG. */
1257 apr_array_header_t *depth_first_catalog_index;
1258
1259 /* A rangelist describing all the revisions potentially merged or
1260 potentially eligible for merging (see FILTERING_MERGED) based on
1261 the target's explicit or inherited mergeinfo. */
1262 const svn_rangelist_t *rangelist;
1263
1264 /* The wrapped svn_log_entry_receiver_t callback and baton which
1265 filter_log_entry_with_rangelist() is acting as a filter for. */
1266 svn_log_entry_receiver_t log_receiver;
1267 void *log_receiver_baton;
1268
1269 svn_client_ctx_t *ctx;
1270 };
1271
1272 /* Implements the svn_log_entry_receiver_t interface. BATON is a
1273 `struct filter_log_entry_baton_t *'.
1274
1275 Call the wrapped log receiver BATON->log_receiver (with
1276 BATON->log_receiver_baton) if:
1277
1278 BATON->FILTERING_MERGED is FALSE and the changes represented by LOG_ENTRY
1279 have been fully merged from BATON->merge_source_fspaths to the WC target
1280 based on the mergeinfo for the WC contained in BATON->TARGET_MERGEINFO_CATALOG.
1281
1282 Or
1283
1284 BATON->FILTERING_MERGED is TRUE and the changes represented by LOG_ENTRY
1285 have not been merged, or only partially merged, from
1286 BATON->merge_source_fspaths to the WC target based on the mergeinfo for the
1287 WC contained in BATON->TARGET_MERGEINFO_CATALOG. */
1288 static svn_error_t *
filter_log_entry_with_rangelist(void * baton,svn_log_entry_t * log_entry,apr_pool_t * pool)1289 filter_log_entry_with_rangelist(void *baton,
1290 svn_log_entry_t *log_entry,
1291 apr_pool_t *pool)
1292 {
1293 struct filter_log_entry_baton_t *fleb = baton;
1294 svn_rangelist_t *intersection, *this_rangelist;
1295
1296 if (fleb->ctx->cancel_func)
1297 SVN_ERR(fleb->ctx->cancel_func(fleb->ctx->cancel_baton));
1298
1299 /* Ignore r0 because there can be no "change 0" in a merge range. */
1300 if (log_entry->revision == 0)
1301 return SVN_NO_ERROR;
1302
1303 this_rangelist = svn_rangelist__initialize(log_entry->revision - 1,
1304 log_entry->revision,
1305 TRUE, pool);
1306
1307 /* Don't consider inheritance yet, see if LOG_ENTRY->REVISION is
1308 fully or partially represented in BATON->RANGELIST. */
1309 SVN_ERR(svn_rangelist_intersect(&intersection, fleb->rangelist,
1310 this_rangelist, FALSE, pool));
1311 if (! (intersection && intersection->nelts))
1312 return SVN_NO_ERROR;
1313
1314 SVN_ERR_ASSERT(intersection->nelts == 1);
1315
1316 /* Ok, we know LOG_ENTRY->REVISION is represented in BATON->RANGELIST,
1317 but is it only partially represented, i.e. is the corresponding range in
1318 BATON->RANGELIST non-inheritable? Ask for the same intersection as
1319 above but consider inheritance this time, if the intersection is empty
1320 we know the range in BATON->RANGELIST is non-inheritable. */
1321 SVN_ERR(svn_rangelist_intersect(&intersection, fleb->rangelist,
1322 this_rangelist, TRUE, pool));
1323 log_entry->non_inheritable = !intersection->nelts;
1324
1325 /* If the paths changed by LOG_ENTRY->REVISION are provided we can determine
1326 if LOG_ENTRY->REVISION, while only partially represented in
1327 BATON->RANGELIST, is in fact completely applied to all affected paths.
1328 ### And ... what if it is, or if it isn't? What do we do with the answer?
1329 And how do we cope if the changed paths are not provided? */
1330 if ((log_entry->non_inheritable || !fleb->filtering_merged)
1331 && log_entry->changed_paths2)
1332 {
1333 apr_hash_index_t *hi;
1334 svn_boolean_t all_subtrees_have_this_rev = TRUE;
1335 svn_rangelist_t *this_rev_rangelist =
1336 svn_rangelist__initialize(log_entry->revision - 1,
1337 log_entry->revision, TRUE, pool);
1338 apr_pool_t *iterpool = svn_pool_create(pool);
1339
1340 for (hi = apr_hash_first(pool, log_entry->changed_paths2);
1341 hi;
1342 hi = apr_hash_next(hi))
1343 {
1344 int i;
1345 const char *path = apr_hash_this_key(hi);
1346 svn_log_changed_path2_t *change = apr_hash_this_val(hi);
1347 const char *target_fspath_affected;
1348 svn_mergeinfo_t nearest_ancestor_mergeinfo;
1349 svn_boolean_t found_this_revision = FALSE;
1350 const char *merge_source_rel_target;
1351 const char *merge_source_fspath;
1352 svn_boolean_t ancestor_is_self;
1353
1354 svn_pool_clear(iterpool);
1355
1356 /* Check that PATH is a subtree of at least one of the
1357 merge sources. If not then ignore this path. */
1358 for (i = 0; i < fleb->merge_source_fspaths->nelts; i++)
1359 {
1360 merge_source_fspath = APR_ARRAY_IDX(fleb->merge_source_fspaths,
1361 i, const char *);
1362
1363 merge_source_rel_target
1364 = svn_fspath__skip_ancestor(merge_source_fspath, path);
1365 if (merge_source_rel_target)
1366 {
1367 /* If MERGE_SOURCE was itself deleted, replaced, or added
1368 in LOG_ENTRY->REVISION then ignore this PATH since you
1369 can't merge a addition or deletion of yourself. */
1370 if (merge_source_rel_target[0] == '\0'
1371 && (change->action != 'M'))
1372 i = fleb->merge_source_fspaths->nelts;
1373 break;
1374 }
1375 }
1376 /* If we examined every merge source path and PATH is a child of
1377 none of them then we can ignore this PATH. */
1378 if (i == fleb->merge_source_fspaths->nelts)
1379 continue;
1380
1381 /* Calculate the target path which PATH would affect if merged. */
1382 target_fspath_affected = svn_fspath__join(fleb->target_fspath,
1383 merge_source_rel_target,
1384 iterpool);
1385
1386 nearest_ancestor_mergeinfo =
1387 find_nearest_ancestor(fleb->depth_first_catalog_index,
1388 &ancestor_is_self,
1389 target_fspath_affected);
1390
1391 /* Issue #3791: A path should never have explicit mergeinfo
1392 describing its own addition (that's self-referential). Nor will
1393 it have explicit mergeinfo describing its own deletion (we
1394 obviously can't add new mergeinfo to a path we are deleting).
1395
1396 This lack of explicit mergeinfo should not cause such revisions
1397 to show up as eligible however. If PATH was deleted, replaced,
1398 or added in LOG_ENTRY->REVISION, but the corresponding
1399 TARGET_PATH_AFFECTED already exists and has explicit mergeinfo
1400 describing merges from PATH *after* LOG_ENTRY->REVISION, then
1401 ignore this PATH. If it was deleted in LOG_ENTRY->REVISION it's
1402 obviously back. If it was added or replaced it's still around
1403 possibly it was replaced one or more times, but it's back now.
1404 Regardless, LOG_ENTRY->REVISION is *not* an eligible revision! */
1405 if (nearest_ancestor_mergeinfo &&
1406 ancestor_is_self /* Explicit mergeinfo on TARGET_PATH_AFFECTED */
1407 && (change->action != 'M'))
1408 {
1409 svn_rangelist_t *rangelist =
1410 svn_hash_gets(nearest_ancestor_mergeinfo, path);
1411 if (rangelist)
1412 {
1413 svn_merge_range_t *youngest_range = APR_ARRAY_IDX(
1414 rangelist, rangelist->nelts - 1, svn_merge_range_t *);
1415
1416 if (youngest_range
1417 && (youngest_range->end > log_entry->revision))
1418 continue;
1419 }
1420 }
1421
1422 if (nearest_ancestor_mergeinfo)
1423 {
1424 apr_hash_index_t *hi2;
1425
1426 for (hi2 = apr_hash_first(iterpool, nearest_ancestor_mergeinfo);
1427 hi2;
1428 hi2 = apr_hash_next(hi2))
1429 {
1430 const char *mergeinfo_path = apr_hash_this_key(hi2);
1431 svn_rangelist_t *rangelist = apr_hash_this_val(hi2);
1432
1433 /* Does the mergeinfo for PATH reflect if
1434 LOG_ENTRY->REVISION was previously merged
1435 from MERGE_SOURCE_FSPATH? */
1436 if (svn_fspath__skip_ancestor(merge_source_fspath,
1437 mergeinfo_path))
1438 {
1439 /* Something was merged from MERGE_SOURCE_FSPATH, does
1440 it include LOG_ENTRY->REVISION? */
1441 SVN_ERR(svn_rangelist_intersect(&intersection,
1442 rangelist,
1443 this_rev_rangelist,
1444 FALSE,
1445 iterpool));
1446 if (intersection->nelts)
1447 {
1448 if (ancestor_is_self)
1449 {
1450 /* TARGET_PATH_AFFECTED has explicit mergeinfo,
1451 so we don't need to worry if that mergeinfo
1452 is inheritable or not. */
1453 found_this_revision = TRUE;
1454 break;
1455 }
1456 else
1457 {
1458 /* TARGET_PATH_AFFECTED inherited its mergeinfo,
1459 so we have to ignore non-inheritable
1460 ranges. */
1461 SVN_ERR(svn_rangelist_intersect(
1462 &intersection,
1463 rangelist,
1464 this_rev_rangelist,
1465 TRUE, iterpool));
1466 if (intersection->nelts)
1467 {
1468 found_this_revision = TRUE;
1469 break;
1470 }
1471 }
1472 }
1473 }
1474 }
1475 }
1476
1477 if (!found_this_revision)
1478 {
1479 /* As soon as any PATH is found that is not fully merged for
1480 LOG_ENTRY->REVISION then we can stop. */
1481 all_subtrees_have_this_rev = FALSE;
1482 break;
1483 }
1484 }
1485
1486 svn_pool_destroy(iterpool);
1487
1488 if (all_subtrees_have_this_rev)
1489 {
1490 if (fleb->filtering_merged)
1491 log_entry->non_inheritable = FALSE;
1492 else
1493 return SVN_NO_ERROR;
1494 }
1495 }
1496
1497 /* Call the wrapped log receiver which this function is filtering for. */
1498 return fleb->log_receiver(fleb->log_receiver_baton, log_entry, pool);
1499 }
1500
1501 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)1502 logs_for_mergeinfo_rangelist(const char *source_url,
1503 const apr_array_header_t *merge_source_fspaths,
1504 svn_boolean_t filtering_merged,
1505 const svn_rangelist_t *rangelist,
1506 svn_boolean_t oldest_revs_first,
1507 svn_mergeinfo_catalog_t target_mergeinfo_catalog,
1508 const char *target_fspath,
1509 svn_boolean_t discover_changed_paths,
1510 const apr_array_header_t *revprops,
1511 svn_log_entry_receiver_t log_receiver,
1512 void *log_receiver_baton,
1513 svn_client_ctx_t *ctx,
1514 svn_ra_session_t *ra_session,
1515 apr_pool_t *scratch_pool)
1516 {
1517 svn_merge_range_t *oldest_range, *youngest_range;
1518 svn_revnum_t oldest_rev, youngest_rev;
1519 struct filter_log_entry_baton_t fleb;
1520
1521 if (! rangelist->nelts)
1522 return SVN_NO_ERROR;
1523
1524 /* Calculate and construct the bounds of our log request. */
1525 youngest_range = APR_ARRAY_IDX(rangelist, rangelist->nelts - 1,
1526 svn_merge_range_t *);
1527 youngest_rev = youngest_range->end;
1528 oldest_range = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *);
1529 oldest_rev = oldest_range->start;
1530
1531 if (! target_mergeinfo_catalog)
1532 target_mergeinfo_catalog = apr_hash_make(scratch_pool);
1533
1534 /* FILTER_LOG_ENTRY_BATON_T->TARGET_MERGEINFO_CATALOG's keys are required
1535 to be repository-absolute. */
1536 SVN_ERR(svn_mergeinfo__add_prefix_to_catalog(&target_mergeinfo_catalog,
1537 target_mergeinfo_catalog, "/",
1538 scratch_pool, scratch_pool));
1539
1540 /* Build the log filtering callback baton. */
1541 fleb.filtering_merged = filtering_merged;
1542 fleb.merge_source_fspaths = merge_source_fspaths;
1543 fleb.target_mergeinfo_catalog = target_mergeinfo_catalog;
1544 fleb.depth_first_catalog_index =
1545 svn_sort__hash(target_mergeinfo_catalog,
1546 svn_sort_compare_items_as_paths,
1547 scratch_pool);
1548 fleb.target_fspath = target_fspath;
1549 fleb.rangelist = rangelist;
1550 fleb.log_receiver = log_receiver;
1551 fleb.log_receiver_baton = log_receiver_baton;
1552 fleb.ctx = ctx;
1553
1554 if (!ra_session)
1555 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, source_url,
1556 NULL, NULL, FALSE, FALSE, ctx,
1557 scratch_pool, scratch_pool));
1558 else
1559 SVN_ERR(svn_ra_reparent(ra_session, source_url, scratch_pool));
1560
1561 {
1562 apr_array_header_t *target;
1563 target = apr_array_make(scratch_pool, 1, sizeof(const char *));
1564 APR_ARRAY_PUSH(target, const char *) = "";
1565
1566 SVN_ERR(svn_ra_get_log2(ra_session, target,
1567 oldest_revs_first ? oldest_rev : youngest_rev,
1568 oldest_revs_first ? youngest_rev : oldest_rev,
1569 0 /* limit */,
1570 discover_changed_paths,
1571 FALSE /* strict_node_history */,
1572 FALSE /* include_merged_revisions */,
1573 revprops,
1574 filter_log_entry_with_rangelist, &fleb,
1575 scratch_pool));
1576 }
1577
1578 /* Check for cancellation. */
1579 if (ctx->cancel_func)
1580 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1581
1582 return SVN_NO_ERROR;
1583 }
1584
1585 /* Set *OUT_MERGEINFO to a shallow copy of MERGEINFO with each source path
1586 converted to a (URI-encoded) URL based on REPOS_ROOT_URL. *OUT_MERGEINFO
1587 is declared as 'apr_hash_t *' because its key do not obey the rules of
1588 'svn_mergeinfo_t'.
1589
1590 Allocate *OUT_MERGEINFO and the new keys in RESULT_POOL. Use
1591 SCRATCH_POOL for any temporary allocations. */
1592 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)1593 mergeinfo_relpaths_to_urls(apr_hash_t **out_mergeinfo,
1594 svn_mergeinfo_t mergeinfo,
1595 const char *repos_root_url,
1596 apr_pool_t *result_pool,
1597 apr_pool_t *scratch_pool)
1598 {
1599 *out_mergeinfo = NULL;
1600 if (mergeinfo)
1601 {
1602 apr_hash_index_t *hi;
1603 apr_hash_t *full_path_mergeinfo = apr_hash_make(result_pool);
1604
1605 for (hi = apr_hash_first(scratch_pool, mergeinfo);
1606 hi; hi = apr_hash_next(hi))
1607 {
1608 const char *key = apr_hash_this_key(hi);
1609 void *val = apr_hash_this_val(hi);
1610
1611 svn_hash_sets(full_path_mergeinfo,
1612 svn_path_url_add_component2(repos_root_url, key + 1,
1613 result_pool),
1614 val);
1615 }
1616 *out_mergeinfo = full_path_mergeinfo;
1617 }
1618
1619 return SVN_NO_ERROR;
1620 }
1621
1622
1623 /*** Public APIs ***/
1624
1625 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)1626 svn_client_mergeinfo_get_merged(apr_hash_t **mergeinfo_p,
1627 const char *path_or_url,
1628 const svn_opt_revision_t *peg_revision,
1629 svn_client_ctx_t *ctx,
1630 apr_pool_t *pool)
1631 {
1632 const char *repos_root;
1633 svn_mergeinfo_catalog_t mergeinfo_cat;
1634 svn_mergeinfo_t mergeinfo;
1635
1636 SVN_ERR(get_mergeinfo(&mergeinfo_cat, &repos_root, path_or_url,
1637 peg_revision, FALSE, FALSE, ctx, NULL, pool, pool));
1638 if (mergeinfo_cat)
1639 {
1640 const char *repos_relpath;
1641
1642 if (! svn_path_is_url(path_or_url))
1643 {
1644 SVN_ERR(svn_dirent_get_absolute(&path_or_url, path_or_url, pool));
1645 SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, NULL, NULL,
1646 ctx->wc_ctx, path_or_url,
1647 pool, pool));
1648 }
1649 else
1650 {
1651 repos_relpath = svn_uri_skip_ancestor(repos_root, path_or_url, pool);
1652
1653 SVN_ERR_ASSERT(repos_relpath != NULL); /* Or get_mergeinfo failed */
1654 }
1655
1656 mergeinfo = svn_hash_gets(mergeinfo_cat, repos_relpath);
1657 }
1658 else
1659 {
1660 mergeinfo = NULL;
1661 }
1662
1663 SVN_ERR(mergeinfo_relpaths_to_urls(mergeinfo_p, mergeinfo,
1664 repos_root, pool, pool));
1665 return SVN_NO_ERROR;
1666 }
1667
1668 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)1669 svn_client__mergeinfo_log(svn_boolean_t finding_merged,
1670 const char *target_path_or_url,
1671 const svn_opt_revision_t *target_peg_revision,
1672 svn_mergeinfo_catalog_t *target_mergeinfo_catalog,
1673 const char *source_path_or_url,
1674 const svn_opt_revision_t *source_peg_revision,
1675 const svn_opt_revision_t *source_start_revision,
1676 const svn_opt_revision_t *source_end_revision,
1677 svn_log_entry_receiver_t log_receiver,
1678 void *log_receiver_baton,
1679 svn_boolean_t discover_changed_paths,
1680 svn_depth_t depth,
1681 const apr_array_header_t *revprops,
1682 svn_client_ctx_t *ctx,
1683 svn_ra_session_t *ra_session,
1684 apr_pool_t *result_pool,
1685 apr_pool_t *scratch_pool)
1686 {
1687 const char *log_target = NULL;
1688 const char *repos_root;
1689 const char *target_repos_relpath;
1690 svn_mergeinfo_catalog_t target_mergeinfo_cat;
1691 svn_ra_session_t *target_session = NULL;
1692 svn_client__pathrev_t *pathrev;
1693
1694 /* A hash of paths, at or under TARGET_PATH_OR_URL, mapped to
1695 rangelists. Not technically mergeinfo, so not using the
1696 svn_mergeinfo_t type. */
1697 apr_hash_t *inheritable_subtree_merges;
1698
1699 svn_mergeinfo_t source_history;
1700 svn_mergeinfo_t target_history;
1701 svn_rangelist_t *master_noninheritable_rangelist;
1702 svn_rangelist_t *master_inheritable_rangelist;
1703 apr_array_header_t *merge_source_fspaths =
1704 apr_array_make(scratch_pool, 1, sizeof(const char *));
1705 apr_hash_index_t *hi_catalog;
1706 apr_hash_index_t *hi;
1707 apr_pool_t *iterpool;
1708 svn_boolean_t oldest_revs_first = TRUE;
1709 apr_pool_t *subpool;
1710
1711 /* We currently only support depth = empty | infinity. */
1712 if (depth != svn_depth_infinity && depth != svn_depth_empty)
1713 return svn_error_create(
1714 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1715 _("Only depths 'infinity' and 'empty' are currently supported"));
1716
1717 /* Validate and sanitize the incoming source operative revision range. */
1718 if (!((source_start_revision->kind == svn_opt_revision_unspecified) ||
1719 (source_start_revision->kind == svn_opt_revision_number) ||
1720 (source_start_revision->kind == svn_opt_revision_date) ||
1721 (source_start_revision->kind == svn_opt_revision_head)))
1722 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
1723 if (!((source_end_revision->kind == svn_opt_revision_unspecified) ||
1724 (source_end_revision->kind == svn_opt_revision_number) ||
1725 (source_end_revision->kind == svn_opt_revision_date) ||
1726 (source_end_revision->kind == svn_opt_revision_head)))
1727 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
1728 if ((source_end_revision->kind != svn_opt_revision_unspecified)
1729 && (source_start_revision->kind == svn_opt_revision_unspecified))
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
1735 subpool = svn_pool_create(scratch_pool);
1736
1737 if (ra_session)
1738 target_session = ra_session;
1739
1740 /* We need the union of TARGET_PATH_OR_URL@TARGET_PEG_REVISION's mergeinfo
1741 and MERGE_SOURCE_URL's history. It's not enough to do path
1742 matching, because renames in the history of MERGE_SOURCE_URL
1743 throw that all in a tizzy. Of course, if there's no mergeinfo on
1744 the target, that vastly simplifies matters (we'll have nothing to
1745 do). */
1746 /* This get_mergeinfo() call doubles as a mergeinfo capabilities check. */
1747 if (target_mergeinfo_catalog)
1748 {
1749 if (*target_mergeinfo_catalog)
1750 {
1751 /* The caller provided the mergeinfo catalog for
1752 TARGET_PATH_OR_URL, so we don't need to accquire
1753 it ourselves. We do need to get the repos_root
1754 though, because get_mergeinfo() won't do it for us. */
1755 target_mergeinfo_cat = *target_mergeinfo_catalog;
1756
1757 if (ra_session && svn_path_is_url(target_path_or_url))
1758 {
1759 SVN_ERR(svn_ra_reparent(ra_session, target_path_or_url, subpool));
1760 SVN_ERR(svn_client__resolve_rev_and_url(&pathrev, ra_session,
1761 target_path_or_url,
1762 target_peg_revision,
1763 target_peg_revision,
1764 ctx, subpool));
1765 target_session = ra_session;
1766 }
1767 else
1768 {
1769 SVN_ERR(svn_client__ra_session_from_path2(&target_session,
1770 &pathrev,
1771 target_path_or_url,
1772 NULL,
1773 target_peg_revision,
1774 target_peg_revision,
1775 ctx, subpool));
1776 }
1777 SVN_ERR(svn_ra_get_repos_root2(target_session, &repos_root,
1778 scratch_pool));
1779 }
1780 else
1781 {
1782 /* The caller didn't provide the mergeinfo catalog for
1783 TARGET_PATH_OR_URL, but wants us to pass a copy back
1784 when we get it, so use RESULT_POOL. */
1785 SVN_ERR(get_mergeinfo(target_mergeinfo_catalog, &repos_root,
1786 target_path_or_url, target_peg_revision,
1787 depth == svn_depth_infinity, TRUE,
1788 ctx, ra_session, result_pool, scratch_pool));
1789 target_mergeinfo_cat = *target_mergeinfo_catalog;
1790 }
1791 }
1792 else
1793 {
1794 /* The caller didn't provide the mergeinfo catalog for
1795 TARGET_PATH_OR_URL, nor does it want a copy, so we can use
1796 nothing but SCRATCH_POOL. */
1797 SVN_ERR(get_mergeinfo(&target_mergeinfo_cat, &repos_root,
1798 target_path_or_url, target_peg_revision,
1799 depth == svn_depth_infinity, TRUE,
1800 ctx, ra_session, scratch_pool, scratch_pool));
1801 }
1802
1803 if (!svn_path_is_url(target_path_or_url))
1804 {
1805 SVN_ERR(svn_dirent_get_absolute(&target_path_or_url,
1806 target_path_or_url, scratch_pool));
1807 SVN_ERR(svn_wc__node_get_repos_info(NULL, &target_repos_relpath,
1808 NULL, NULL,
1809 ctx->wc_ctx, target_path_or_url,
1810 scratch_pool, scratch_pool));
1811 }
1812 else
1813 {
1814 target_repos_relpath = svn_uri_skip_ancestor(repos_root,
1815 target_path_or_url,
1816 scratch_pool);
1817
1818 /* TARGET_REPOS_REL should be non-NULL, else get_mergeinfo
1819 should have failed. */
1820 SVN_ERR_ASSERT(target_repos_relpath != NULL);
1821 }
1822
1823 if (!target_mergeinfo_cat)
1824 {
1825 /* If we are looking for what has been merged and there is no
1826 mergeinfo then we already know the answer. If we are looking
1827 for eligible revisions then create a catalog with empty mergeinfo
1828 on the target. This is semantically equivalent to no mergeinfo
1829 and gives us something to combine with MERGE_SOURCE_URL's
1830 history. */
1831 if (finding_merged)
1832 {
1833 svn_pool_destroy(subpool);
1834 return SVN_NO_ERROR;
1835 }
1836 else
1837 {
1838 target_mergeinfo_cat = apr_hash_make(scratch_pool);
1839 svn_hash_sets(target_mergeinfo_cat, target_repos_relpath,
1840 apr_hash_make(scratch_pool));
1841 }
1842 }
1843
1844 /* Fetch the location history as mergeinfo, for the source branch
1845 * (between the given start and end revisions), and, if we're finding
1846 * merged revisions, then also for the entire target branch.
1847 *
1848 * ### TODO: As the source and target must be in the same repository, we
1849 * should share a single session, tracking the two URLs separately. */
1850 {
1851 svn_ra_session_t *source_session;
1852 svn_revnum_t start_rev, end_rev, youngest_rev = SVN_INVALID_REVNUM;
1853
1854 if (! finding_merged)
1855 {
1856 if (!target_session)
1857 SVN_ERR(svn_client__ra_session_from_path2(&target_session, &pathrev,
1858 target_path_or_url, NULL,
1859 target_peg_revision,
1860 target_peg_revision,
1861 ctx, subpool));
1862 SVN_ERR(svn_client__get_history_as_mergeinfo(&target_history, NULL,
1863 pathrev,
1864 SVN_INVALID_REVNUM,
1865 SVN_INVALID_REVNUM,
1866 target_session, ctx,
1867 scratch_pool));
1868 }
1869
1870 if (target_session
1871 && svn_path_is_url(source_path_or_url)
1872 && repos_root
1873 && svn_uri_skip_ancestor(repos_root, source_path_or_url, subpool))
1874 {
1875 /* We can re-use the existing session */
1876 source_session = target_session;
1877 SVN_ERR(svn_ra_reparent(source_session, source_path_or_url, subpool));
1878 SVN_ERR(svn_client__resolve_rev_and_url(&pathrev, source_session,
1879 source_path_or_url,
1880 source_peg_revision,
1881 source_peg_revision,
1882 ctx, subpool));
1883 }
1884 else
1885 {
1886 SVN_ERR(svn_client__ra_session_from_path2(&source_session, &pathrev,
1887 source_path_or_url, NULL,
1888 source_peg_revision,
1889 source_peg_revision,
1890 ctx, subpool));
1891 }
1892 SVN_ERR(svn_client__get_revision_number(&start_rev, &youngest_rev,
1893 ctx->wc_ctx, source_path_or_url,
1894 source_session,
1895 source_start_revision,
1896 subpool));
1897 SVN_ERR(svn_client__get_revision_number(&end_rev, &youngest_rev,
1898 ctx->wc_ctx, source_path_or_url,
1899 source_session,
1900 source_end_revision,
1901 subpool));
1902 SVN_ERR(svn_client__get_history_as_mergeinfo(&source_history, NULL,
1903 pathrev,
1904 MAX(end_rev, start_rev),
1905 MIN(end_rev, start_rev),
1906 source_session, ctx,
1907 scratch_pool));
1908 if (start_rev > end_rev)
1909 oldest_revs_first = FALSE;
1910 }
1911
1912 /* Separate the explicit or inherited mergeinfo on TARGET_PATH_OR_URL,
1913 and possibly its explicit subtree mergeinfo, into their
1914 inheritable and non-inheritable parts. */
1915 master_noninheritable_rangelist = apr_array_make(scratch_pool, 64,
1916 sizeof(svn_merge_range_t *));
1917 master_inheritable_rangelist = apr_array_make(scratch_pool, 64,
1918 sizeof(svn_merge_range_t *));
1919 inheritable_subtree_merges = apr_hash_make(scratch_pool);
1920
1921 iterpool = svn_pool_create(scratch_pool);
1922
1923 for (hi_catalog = apr_hash_first(scratch_pool, target_mergeinfo_cat);
1924 hi_catalog;
1925 hi_catalog = apr_hash_next(hi_catalog))
1926 {
1927 svn_mergeinfo_t subtree_mergeinfo = apr_hash_this_val(hi_catalog);
1928 svn_mergeinfo_t subtree_history;
1929 svn_mergeinfo_t subtree_source_history;
1930 svn_mergeinfo_t subtree_inheritable_mergeinfo;
1931 svn_mergeinfo_t subtree_noninheritable_mergeinfo;
1932 svn_mergeinfo_t merged_noninheritable;
1933 svn_mergeinfo_t merged;
1934 const char *subtree_path = apr_hash_this_key(hi_catalog);
1935 svn_boolean_t is_subtree = strcmp(subtree_path,
1936 target_repos_relpath) != 0;
1937 svn_pool_clear(iterpool);
1938
1939 if (is_subtree)
1940 {
1941 /* If SUBTREE_PATH is a proper subtree of TARGET_PATH_OR_URL
1942 then make a copy of SOURCE_HISTORY that is path adjusted
1943 for the subtree. */
1944 const char *subtree_rel_path =
1945 subtree_path + strlen(target_repos_relpath) + 1;
1946
1947 SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
1948 &subtree_source_history, source_history,
1949 subtree_rel_path, scratch_pool, iterpool));
1950
1951 if (!finding_merged)
1952 SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
1953 &subtree_history, target_history,
1954 subtree_rel_path, scratch_pool, iterpool));
1955 }
1956 else
1957 {
1958 subtree_source_history = source_history;
1959 if (!finding_merged)
1960 subtree_history = target_history;
1961 }
1962
1963 if (!finding_merged)
1964 {
1965 svn_mergeinfo_t merged_via_history;
1966 SVN_ERR(svn_mergeinfo_intersect2(&merged_via_history,
1967 subtree_history,
1968 subtree_source_history, TRUE,
1969 scratch_pool, iterpool));
1970 SVN_ERR(svn_mergeinfo_merge2(subtree_mergeinfo,
1971 merged_via_history,
1972 scratch_pool, iterpool));
1973 }
1974
1975 SVN_ERR(svn_mergeinfo_inheritable2(&subtree_inheritable_mergeinfo,
1976 subtree_mergeinfo, NULL,
1977 SVN_INVALID_REVNUM,
1978 SVN_INVALID_REVNUM,
1979 TRUE, scratch_pool, iterpool));
1980 SVN_ERR(svn_mergeinfo_inheritable2(&subtree_noninheritable_mergeinfo,
1981 subtree_mergeinfo, NULL,
1982 SVN_INVALID_REVNUM,
1983 SVN_INVALID_REVNUM,
1984 FALSE, scratch_pool, iterpool));
1985
1986 /* Find the intersection of the non-inheritable part of
1987 SUBTREE_MERGEINFO and SOURCE_HISTORY. svn_mergeinfo_intersect2()
1988 won't consider non-inheritable and inheritable ranges
1989 intersecting unless we ignore inheritance, but in doing so the
1990 resulting intersections have all inheritable ranges. To get
1991 around this we set the inheritance on the result to all
1992 non-inheritable. */
1993 SVN_ERR(svn_mergeinfo_intersect2(&merged_noninheritable,
1994 subtree_noninheritable_mergeinfo,
1995 subtree_source_history, FALSE,
1996 scratch_pool, iterpool));
1997 svn_mergeinfo__set_inheritance(merged_noninheritable, FALSE,
1998 iterpool);
1999
2000 /* Keep track of all ranges partially merged to any and all
2001 subtrees. */
2002 SVN_ERR(svn_rangelist__merge_many(master_noninheritable_rangelist,
2003 merged_noninheritable,
2004 scratch_pool, iterpool));
2005
2006 /* Find the intersection of the inheritable part of TGT_MERGEINFO
2007 and SOURCE_HISTORY. */
2008 SVN_ERR(svn_mergeinfo_intersect2(&merged,
2009 subtree_inheritable_mergeinfo,
2010 subtree_source_history, FALSE,
2011 scratch_pool, iterpool));
2012
2013 /* Keep track of all ranges fully merged to any and all
2014 subtrees. */
2015 if (apr_hash_count(merged))
2016 {
2017 /* The inheritable rangelist merged from SUBTREE_SOURCE_HISTORY
2018 to SUBTREE_PATH. */
2019 svn_rangelist_t *subtree_merged_rangelist =
2020 apr_array_make(scratch_pool, 1, sizeof(svn_merge_range_t *));
2021
2022 SVN_ERR(svn_rangelist__merge_many(master_inheritable_rangelist,
2023 merged, scratch_pool, iterpool));
2024 SVN_ERR(svn_rangelist__merge_many(subtree_merged_rangelist,
2025 merged, scratch_pool, iterpool));
2026
2027 svn_hash_sets(inheritable_subtree_merges, subtree_path,
2028 subtree_merged_rangelist);
2029 }
2030 else
2031 {
2032 /* Map SUBTREE_PATH to an empty rangelist if there was nothing
2033 fully merged. e.g. Only empty or non-inheritable mergeinfo
2034 on the subtree or mergeinfo unrelated to the source. */
2035 svn_hash_sets(inheritable_subtree_merges, subtree_path,
2036 apr_array_make(scratch_pool, 0,
2037 sizeof(svn_merge_range_t *)));
2038 }
2039 }
2040
2041 /* Make sure every range in MASTER_INHERITABLE_RANGELIST is fully merged to
2042 each subtree (including the target itself). Any revisions which don't
2043 exist in *every* subtree are *potentially* only partially merged to the
2044 tree rooted at TARGET_PATH_OR_URL, so move those revisions to
2045 MASTER_NONINHERITABLE_RANGELIST. It may turn out that that a revision
2046 was merged to the only subtree it affects, but we need to examine the
2047 logs to make this determination (which will be done by
2048 logs_for_mergeinfo_rangelist). */
2049 if (master_inheritable_rangelist->nelts)
2050 {
2051 for (hi = apr_hash_first(scratch_pool, inheritable_subtree_merges);
2052 hi;
2053 hi = apr_hash_next(hi))
2054 {
2055 svn_rangelist_t *deleted_rangelist;
2056 svn_rangelist_t *added_rangelist;
2057 svn_rangelist_t *subtree_merged_rangelist = apr_hash_this_val(hi);
2058
2059 svn_pool_clear(iterpool);
2060
2061 SVN_ERR(svn_rangelist_diff(&deleted_rangelist, &added_rangelist,
2062 master_inheritable_rangelist,
2063 subtree_merged_rangelist, TRUE,
2064 iterpool));
2065
2066 if (deleted_rangelist->nelts)
2067 {
2068 svn_rangelist__set_inheritance(deleted_rangelist, FALSE);
2069 SVN_ERR(svn_rangelist_merge2(master_noninheritable_rangelist,
2070 deleted_rangelist,
2071 scratch_pool, iterpool));
2072 SVN_ERR(svn_rangelist_remove(&master_inheritable_rangelist,
2073 deleted_rangelist,
2074 master_inheritable_rangelist,
2075 FALSE,
2076 scratch_pool));
2077 }
2078 }
2079 }
2080
2081 if (finding_merged)
2082 {
2083 /* Roll all the merged revisions into one rangelist. */
2084 SVN_ERR(svn_rangelist_merge2(master_inheritable_rangelist,
2085 master_noninheritable_rangelist,
2086 scratch_pool, scratch_pool));
2087
2088 }
2089 else
2090 {
2091 /* Create the starting rangelist for what might be eligible. */
2092 svn_rangelist_t *source_master_rangelist =
2093 apr_array_make(scratch_pool, 1, sizeof(svn_merge_range_t *));
2094
2095 SVN_ERR(svn_rangelist__merge_many(source_master_rangelist,
2096 source_history,
2097 scratch_pool, scratch_pool));
2098
2099 /* From what might be eligible subtract what we know is
2100 partially merged and then merge that back. */
2101 SVN_ERR(svn_rangelist_remove(&source_master_rangelist,
2102 master_noninheritable_rangelist,
2103 source_master_rangelist,
2104 FALSE, scratch_pool));
2105 SVN_ERR(svn_rangelist_merge2(source_master_rangelist,
2106 master_noninheritable_rangelist,
2107 scratch_pool, scratch_pool));
2108 SVN_ERR(svn_rangelist_remove(&master_inheritable_rangelist,
2109 master_inheritable_rangelist,
2110 source_master_rangelist,
2111 TRUE, scratch_pool));
2112 }
2113
2114 /* Nothing merged? Not even when considering shared history if
2115 looking for eligible revisions (i.e. !FINDING_MERGED)? Then there
2116 is nothing more to do. */
2117 if (! master_inheritable_rangelist->nelts)
2118 {
2119 svn_pool_destroy(iterpool);
2120 return SVN_NO_ERROR;
2121 }
2122 else
2123 {
2124 /* Determine the correct (youngest) target for 'svn log'. */
2125 svn_merge_range_t *youngest_range
2126 = APR_ARRAY_IDX(master_inheritable_rangelist,
2127 master_inheritable_rangelist->nelts - 1,
2128 svn_merge_range_t *);
2129 svn_rangelist_t *youngest_rangelist =
2130 svn_rangelist__initialize(youngest_range->end - 1,
2131 youngest_range->end,
2132 youngest_range->inheritable,
2133 scratch_pool);
2134
2135 for (hi = apr_hash_first(scratch_pool, source_history);
2136 hi;
2137 hi = apr_hash_next(hi))
2138 {
2139 const char *key = apr_hash_this_key(hi);
2140 svn_rangelist_t *subtree_merged_rangelist = apr_hash_this_val(hi);
2141 svn_rangelist_t *intersecting_rangelist;
2142
2143 svn_pool_clear(iterpool);
2144 SVN_ERR(svn_rangelist_intersect(&intersecting_rangelist,
2145 youngest_rangelist,
2146 subtree_merged_rangelist,
2147 FALSE, iterpool));
2148
2149 APR_ARRAY_PUSH(merge_source_fspaths, const char *) = key;
2150
2151 if (intersecting_rangelist->nelts)
2152 log_target = key;
2153 }
2154 }
2155
2156 svn_pool_destroy(iterpool);
2157
2158 /* Step 4: Finally, we run 'svn log' to drive our log receiver, but
2159 using a receiver filter to only allow revisions to pass through
2160 that are in our rangelist. */
2161 log_target = svn_path_url_add_component2(repos_root, log_target + 1,
2162 scratch_pool);
2163
2164 {
2165 svn_error_t *err;
2166
2167 err = logs_for_mergeinfo_rangelist(log_target, merge_source_fspaths,
2168 finding_merged,
2169 master_inheritable_rangelist,
2170 oldest_revs_first,
2171 target_mergeinfo_cat,
2172 svn_fspath__join("/",
2173 target_repos_relpath,
2174 scratch_pool),
2175 discover_changed_paths,
2176 revprops,
2177 log_receiver, log_receiver_baton,
2178 ctx, target_session, scratch_pool);
2179
2180 /* Close the source and target sessions. */
2181 svn_pool_destroy(subpool); /* For SVN_ERR_CEASE_INVOCATION */
2182
2183 return svn_error_trace(err);
2184 }
2185 }
2186
2187 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)2188 svn_client_mergeinfo_log2(svn_boolean_t finding_merged,
2189 const char *target_path_or_url,
2190 const svn_opt_revision_t *target_peg_revision,
2191 const char *source_path_or_url,
2192 const svn_opt_revision_t *source_peg_revision,
2193 const svn_opt_revision_t *source_start_revision,
2194 const svn_opt_revision_t *source_end_revision,
2195 svn_log_entry_receiver_t log_receiver,
2196 void *log_receiver_baton,
2197 svn_boolean_t discover_changed_paths,
2198 svn_depth_t depth,
2199 const apr_array_header_t *revprops,
2200 svn_client_ctx_t *ctx,
2201 apr_pool_t *scratch_pool)
2202 {
2203 return svn_error_trace(
2204 svn_client__mergeinfo_log(finding_merged, target_path_or_url,
2205 target_peg_revision, NULL,
2206 source_path_or_url, source_peg_revision,
2207 source_start_revision, source_end_revision,
2208 log_receiver, log_receiver_baton,
2209 discover_changed_paths, depth, revprops,
2210 ctx, NULL,
2211 scratch_pool, scratch_pool));
2212 }
2213
2214 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)2215 svn_client_suggest_merge_sources(apr_array_header_t **suggestions,
2216 const char *path_or_url,
2217 const svn_opt_revision_t *peg_revision,
2218 svn_client_ctx_t *ctx,
2219 apr_pool_t *pool)
2220 {
2221 const char *repos_root;
2222 const char *copyfrom_path;
2223 apr_array_header_t *list;
2224 svn_revnum_t copyfrom_rev;
2225 svn_mergeinfo_catalog_t mergeinfo_cat;
2226 svn_mergeinfo_t mergeinfo;
2227 apr_hash_index_t *hi;
2228 apr_pool_t *session_pool = svn_pool_create(pool);
2229 svn_ra_session_t *ra_session;
2230
2231 list = apr_array_make(pool, 1, sizeof(const char *));
2232
2233 /* In our ideal algorithm, the list of recommendations should be
2234 ordered by:
2235
2236 1. The most recent existing merge source.
2237 2. The copyfrom source (which will also be listed as a merge
2238 source if the copy was made with a 1.5+ client and server).
2239 3. All other merge sources, most recent to least recent.
2240
2241 However, determining the order of application of merge sources
2242 requires a new RA API. Until such an API is available, our
2243 algorithm will be:
2244
2245 1. The copyfrom source.
2246 2. All remaining merge sources (unordered).
2247 */
2248 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, NULL, path_or_url,
2249 NULL, peg_revision, peg_revision,
2250 ctx, session_pool));
2251
2252 SVN_ERR(get_mergeinfo(&mergeinfo_cat, &repos_root, path_or_url,
2253 peg_revision, FALSE, FALSE,
2254 ctx, ra_session, session_pool, session_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 = apr_hash_this_val(apr_hash_first(session_pool,
2262 mergeinfo_cat));
2263 }
2264 else
2265 {
2266 mergeinfo = NULL;
2267 }
2268
2269 /* ### Should we only add the last source or all copy sources back to
2270 the origin? */
2271 SVN_ERR(svn_client__get_copy_source(©from_path, ©from_rev,
2272 path_or_url, peg_revision, ra_session,
2273 ctx, session_pool, session_pool));
2274 if (copyfrom_path)
2275 {
2276 APR_ARRAY_PUSH(list, const char *) =
2277 svn_path_url_add_component2(repos_root, copyfrom_path, pool);
2278 }
2279
2280 if (mergeinfo)
2281 {
2282 for (hi = apr_hash_first(session_pool, mergeinfo);
2283 hi;
2284 hi = apr_hash_next(hi))
2285 {
2286 const char *rel_path = apr_hash_this_key(hi);
2287
2288 if (copyfrom_path == NULL || strcmp(rel_path, copyfrom_path) != 0)
2289 APR_ARRAY_PUSH(list, const char *) = \
2290 svn_path_url_add_component2(repos_root, rel_path + 1, pool);
2291 }
2292 }
2293
2294 svn_pool_destroy(session_pool);
2295
2296 *suggestions = list;
2297 return SVN_NO_ERROR;
2298 }
2299
2300 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)2301 svn_client__mergeinfo_status(svn_boolean_t *mergeinfo_changes,
2302 svn_wc_context_t *wc_ctx,
2303 const char *local_abspath,
2304 apr_pool_t *scratch_pool)
2305 {
2306 apr_array_header_t *propchanges;
2307 int i;
2308
2309 *mergeinfo_changes = FALSE;
2310
2311 SVN_ERR(svn_wc_get_prop_diffs2(&propchanges, NULL, wc_ctx,
2312 local_abspath, scratch_pool, scratch_pool));
2313
2314 for (i = 0; i < propchanges->nelts; i++)
2315 {
2316 svn_prop_t prop = APR_ARRAY_IDX(propchanges, i, svn_prop_t);
2317 if (strcmp(prop.name, SVN_PROP_MERGEINFO) == 0)
2318 {
2319 *mergeinfo_changes = TRUE;
2320 break;
2321 }
2322 }
2323
2324 return SVN_NO_ERROR;
2325 }
2326