xref: /trueos/contrib/subversion/subversion/libsvn_client/mergeinfo.c (revision 7cfbe47f2542407346168136f8e33b5015eda94f)
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(&copyfrom_path, &copyfrom_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