1251881Speter/*
2251881Speter * mergeinfo.c :  merge history functions for the libsvn_client library
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter#include <apr_pools.h>
25251881Speter#include <apr_strings.h>
26251881Speter
27251881Speter#include "svn_pools.h"
28251881Speter#include "svn_dirent_uri.h"
29251881Speter#include "svn_path.h"
30251881Speter#include "svn_string.h"
31251881Speter#include "svn_opt.h"
32251881Speter#include "svn_error.h"
33251881Speter#include "svn_error_codes.h"
34251881Speter#include "svn_props.h"
35251881Speter#include "svn_mergeinfo.h"
36251881Speter#include "svn_sorts.h"
37251881Speter#include "svn_ra.h"
38251881Speter#include "svn_client.h"
39251881Speter#include "svn_hash.h"
40251881Speter
41289180Speter#include "private/svn_client_private.h"
42251881Speter#include "private/svn_opt_private.h"
43251881Speter#include "private/svn_mergeinfo_private.h"
44289180Speter#include "private/svn_ra_private.h"
45289180Speter#include "private/svn_sorts_private.h"
46251881Speter#include "private/svn_wc_private.h"
47251881Speter#include "private/svn_fspath.h"
48251881Speter#include "client.h"
49251881Speter#include "mergeinfo.h"
50251881Speter#include "svn_private_config.h"
51251881Speter
52251881Speter
53251881Speter
54251881Spetersvn_client__merge_path_t *
55251881Spetersvn_client__merge_path_dup(const svn_client__merge_path_t *old,
56251881Speter                           apr_pool_t *pool)
57251881Speter{
58251881Speter  svn_client__merge_path_t *new = apr_pmemdup(pool, old, sizeof(*old));
59251881Speter
60251881Speter  new->abspath = apr_pstrdup(pool, old->abspath);
61251881Speter  if (new->remaining_ranges)
62251881Speter    new->remaining_ranges = svn_rangelist_dup(old->remaining_ranges, pool);
63251881Speter  if (new->pre_merge_mergeinfo)
64251881Speter    new->pre_merge_mergeinfo = svn_mergeinfo_dup(old->pre_merge_mergeinfo,
65251881Speter                                                 pool);
66251881Speter  if (new->implicit_mergeinfo)
67251881Speter    new->implicit_mergeinfo = svn_mergeinfo_dup(old->implicit_mergeinfo,
68251881Speter                                                pool);
69251881Speter
70251881Speter  return new;
71251881Speter}
72251881Speter
73251881Spetersvn_client__merge_path_t *
74251881Spetersvn_client__merge_path_create(const char *abspath,
75251881Speter                              apr_pool_t *pool)
76251881Speter{
77251881Speter  svn_client__merge_path_t *result = apr_pcalloc(pool, sizeof(*result));
78251881Speter
79251881Speter  result->abspath = apr_pstrdup(pool, abspath);
80251881Speter  return result;
81251881Speter}
82251881Speter
83251881Spetersvn_error_t *
84251881Spetersvn_client__parse_mergeinfo(svn_mergeinfo_t *mergeinfo,
85251881Speter                            svn_wc_context_t *wc_ctx,
86251881Speter                            const char *local_abspath,
87251881Speter                            apr_pool_t *result_pool,
88251881Speter                            apr_pool_t *scratch_pool)
89251881Speter{
90251881Speter  const svn_string_t *propval;
91251881Speter
92251881Speter  *mergeinfo = NULL;
93251881Speter
94251881Speter  /* ### Use svn_wc_prop_get() would actually be sufficient for now.
95251881Speter     ### DannyB thinks that later we'll need behavior more like
96251881Speter     ### svn_client__get_prop_from_wc(). */
97251881Speter  SVN_ERR(svn_wc_prop_get2(&propval, wc_ctx, local_abspath, SVN_PROP_MERGEINFO,
98251881Speter                           scratch_pool, scratch_pool));
99251881Speter  if (propval)
100251881Speter    SVN_ERR(svn_mergeinfo_parse(mergeinfo, propval->data, result_pool));
101251881Speter
102251881Speter  return SVN_NO_ERROR;
103251881Speter}
104251881Speter
105251881Spetersvn_error_t *
106251881Spetersvn_client__record_wc_mergeinfo(const char *local_abspath,
107251881Speter                                svn_mergeinfo_t mergeinfo,
108251881Speter                                svn_boolean_t do_notification,
109251881Speter                                svn_client_ctx_t *ctx,
110251881Speter                                apr_pool_t *scratch_pool)
111251881Speter{
112251881Speter  svn_string_t *mergeinfo_str = NULL;
113251881Speter  svn_boolean_t mergeinfo_changes = FALSE;
114251881Speter
115251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
116251881Speter
117251881Speter  /* Convert MERGEINFO (if any) into text for storage as a property value. */
118251881Speter  if (mergeinfo)
119251881Speter    SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_str, mergeinfo, scratch_pool));
120251881Speter
121251881Speter  if (do_notification && ctx->notify_func2)
122251881Speter    SVN_ERR(svn_client__mergeinfo_status(&mergeinfo_changes, ctx->wc_ctx,
123251881Speter                                         local_abspath, scratch_pool));
124251881Speter
125251881Speter  /* Record the new mergeinfo in the WC. */
126251881Speter  /* ### Later, we'll want behavior more analogous to
127251881Speter     ### svn_client__get_prop_from_wc(). */
128251881Speter  SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, SVN_PROP_MERGEINFO,
129251881Speter                           mergeinfo_str, svn_depth_empty,
130251881Speter                           TRUE /* skip checks */, NULL,
131251881Speter                           NULL, NULL /* cancellation */,
132251881Speter                           NULL, NULL /* notification */,
133251881Speter                           scratch_pool));
134251881Speter
135251881Speter  if (do_notification && ctx->notify_func2)
136251881Speter    {
137251881Speter      svn_wc_notify_t *notify =
138251881Speter        svn_wc_create_notify(local_abspath,
139251881Speter                             svn_wc_notify_merge_record_info,
140251881Speter                             scratch_pool);
141251881Speter      if (mergeinfo_changes)
142251881Speter        notify->prop_state = svn_wc_notify_state_merged;
143251881Speter      else
144251881Speter        notify->prop_state = svn_wc_notify_state_changed;
145251881Speter
146251881Speter      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
147251881Speter    }
148251881Speter
149251881Speter  return SVN_NO_ERROR;
150251881Speter}
151251881Speter
152251881Spetersvn_error_t *
153251881Spetersvn_client__record_wc_mergeinfo_catalog(apr_hash_t *result_catalog,
154251881Speter                                        svn_client_ctx_t *ctx,
155251881Speter                                        apr_pool_t *scratch_pool)
156251881Speter{
157251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
158251881Speter
159251881Speter  if (apr_hash_count(result_catalog))
160251881Speter    {
161251881Speter      int i;
162251881Speter      apr_array_header_t *sorted_cat =
163251881Speter        svn_sort__hash(result_catalog, svn_sort_compare_items_as_paths,
164251881Speter                       scratch_pool);
165251881Speter
166251881Speter      /* Write the mergeinfo out in sorted order of the paths (presumably just
167251881Speter       * so that the notifications are in a predictable, convenient order). */
168251881Speter      for (i = 0; i < sorted_cat->nelts; i++)
169251881Speter        {
170251881Speter          svn_sort__item_t elt = APR_ARRAY_IDX(sorted_cat, i,
171251881Speter                                               svn_sort__item_t);
172251881Speter          svn_error_t *err;
173251881Speter
174251881Speter          svn_pool_clear(iterpool);
175251881Speter          err = svn_client__record_wc_mergeinfo(elt.key, elt.value, TRUE,
176251881Speter                                                ctx, iterpool);
177251881Speter
178251881Speter          if (err && err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
179251881Speter            {
180251881Speter              /* PATH isn't just missing, it's not even versioned as far
181251881Speter                 as this working copy knows.  But it was included in
182251881Speter                 MERGES, which means that the server knows about it.
183251881Speter                 Likely we don't have access to the source due to authz
184251881Speter                 restrictions.  For now just clear the error and
185251881Speter                 continue... */
186251881Speter              svn_error_clear(err);
187251881Speter            }
188251881Speter          else
189251881Speter            {
190251881Speter              SVN_ERR(err);
191251881Speter            }
192251881Speter        }
193251881Speter    }
194251881Speter  svn_pool_destroy(iterpool);
195251881Speter  return SVN_NO_ERROR;
196251881Speter}
197251881Speter
198251881Speter/*-----------------------------------------------------------------------*/
199251881Speter
200251881Speter/*** Retrieving mergeinfo. ***/
201251881Speter
202251881Spetersvn_error_t *
203251881Spetersvn_client__get_wc_mergeinfo(svn_mergeinfo_t *mergeinfo,
204251881Speter                             svn_boolean_t *inherited_p,
205251881Speter                             svn_mergeinfo_inheritance_t inherit,
206251881Speter                             const char *local_abspath,
207251881Speter                             const char *limit_abspath,
208251881Speter                             const char **walked_path,
209251881Speter                             svn_boolean_t ignore_invalid_mergeinfo,
210251881Speter                             svn_client_ctx_t *ctx,
211251881Speter                             apr_pool_t *result_pool,
212251881Speter                             apr_pool_t *scratch_pool)
213251881Speter{
214251881Speter  const char *walk_relpath = "";
215251881Speter  svn_mergeinfo_t wc_mergeinfo;
216251881Speter  svn_revnum_t base_revision;
217251881Speter  apr_pool_t *iterpool;
218251881Speter  svn_boolean_t inherited;
219251881Speter
220251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
221251881Speter  if (limit_abspath)
222251881Speter    SVN_ERR_ASSERT(svn_dirent_is_absolute(limit_abspath));
223251881Speter
224251881Speter  SVN_ERR(svn_wc__node_get_base(NULL, &base_revision, NULL, NULL, NULL, NULL,
225251881Speter                                ctx->wc_ctx, local_abspath,
226251881Speter                                TRUE /* ignore_enoent */,
227251881Speter                                scratch_pool, scratch_pool));
228251881Speter
229251881Speter  iterpool = svn_pool_create(scratch_pool);
230251881Speter  while (TRUE)
231251881Speter    {
232251881Speter      svn_pool_clear(iterpool);
233251881Speter
234251881Speter      /* Don't look for explicit mergeinfo on LOCAL_ABSPATH if we are only
235251881Speter         interested in inherited mergeinfo. */
236251881Speter      if (inherit == svn_mergeinfo_nearest_ancestor)
237251881Speter        {
238251881Speter          wc_mergeinfo = NULL;
239251881Speter          inherit = svn_mergeinfo_inherited;
240251881Speter        }
241251881Speter      else
242251881Speter        {
243251881Speter          /* Look for mergeinfo on LOCAL_ABSPATH.  If there isn't any and we
244251881Speter             want inherited mergeinfo, walk towards the root of the WC until
245251881Speter             we encounter either (a) an unversioned directory, or
246251881Speter             (b) mergeinfo.  If we encounter (b), use that inherited
247251881Speter             mergeinfo as our baseline. */
248251881Speter          svn_error_t *err = svn_client__parse_mergeinfo(&wc_mergeinfo,
249251881Speter                                                         ctx->wc_ctx,
250251881Speter                                                         local_abspath,
251251881Speter                                                         result_pool,
252251881Speter                                                         iterpool);
253251881Speter          if ((ignore_invalid_mergeinfo || walk_relpath [0] != '\0')
254251881Speter              && err
255251881Speter              && err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
256251881Speter            {
257251881Speter              svn_error_clear(err);
258251881Speter              wc_mergeinfo = apr_hash_make(result_pool);
259251881Speter              break;
260251881Speter            }
261251881Speter          else
262251881Speter            {
263251881Speter              SVN_ERR(err);
264251881Speter            }
265251881Speter        }
266251881Speter
267251881Speter      if (wc_mergeinfo == NULL &&
268251881Speter          inherit != svn_mergeinfo_explicit &&
269251881Speter          !svn_dirent_is_root(local_abspath, strlen(local_abspath)))
270251881Speter        {
271251881Speter          svn_boolean_t is_wc_root;
272251881Speter          svn_boolean_t is_switched;
273251881Speter          svn_revnum_t parent_base_rev;
274251881Speter          svn_revnum_t parent_changed_rev;
275251881Speter
276251881Speter          /* Don't look any higher than the limit path. */
277251881Speter          if (limit_abspath && strcmp(limit_abspath, local_abspath) == 0)
278251881Speter            break;
279251881Speter
280251881Speter          /* If we've reached the root of the working copy don't look any
281251881Speter             higher. */
282251881Speter          SVN_ERR(svn_wc_check_root(&is_wc_root, &is_switched, NULL,
283251881Speter                                    ctx->wc_ctx, local_abspath, iterpool));
284251881Speter          if (is_wc_root || is_switched)
285251881Speter            break;
286251881Speter
287251881Speter          /* No explicit mergeinfo on this path.  Look higher up the
288251881Speter             directory tree while keeping track of what we've walked. */
289251881Speter          walk_relpath = svn_relpath_join(svn_dirent_basename(local_abspath,
290251881Speter                                                              iterpool),
291251881Speter                                          walk_relpath, result_pool);
292251881Speter          local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
293251881Speter
294251881Speter          SVN_ERR(svn_wc__node_get_base(NULL, &parent_base_rev, NULL, NULL,
295251881Speter                                        NULL, NULL,
296251881Speter                                        ctx->wc_ctx, local_abspath,
297289180Speter                                        TRUE /* ignore_enoent */,
298251881Speter                                        scratch_pool, scratch_pool));
299251881Speter
300251881Speter          /* ### This checks the WORKING changed_rev, so invalid on replacement
301251881Speter             ### not even reliable in case an ancestor was copied from a
302251881Speter             ### different location */
303251881Speter          SVN_ERR(svn_wc__node_get_changed_info(&parent_changed_rev,
304251881Speter                                                NULL, NULL,
305251881Speter                                                ctx->wc_ctx, local_abspath,
306251881Speter                                                scratch_pool,
307251881Speter                                                scratch_pool));
308251881Speter
309251881Speter          /* Look in LOCAL_ABSPATH's parent for inherited mergeinfo if
310251881Speter             LOCAL_ABSPATH has no base revision because it is an uncommitted
311251881Speter             addition, or if its base revision falls within the inclusive
312251881Speter             range of its parent's last changed revision to the parent's base
313251881Speter             revision; otherwise stop looking for inherited mergeinfo. */
314251881Speter          if (SVN_IS_VALID_REVNUM(base_revision)
315251881Speter              && (base_revision < parent_changed_rev
316251881Speter                  || parent_base_rev < base_revision))
317251881Speter            break;
318251881Speter
319251881Speter          /* We haven't yet risen above the root of the WC. */
320251881Speter          continue;
321251881Speter        }
322251881Speter      break;
323251881Speter    }
324251881Speter
325251881Speter  svn_pool_destroy(iterpool);
326251881Speter
327251881Speter  if (svn_path_is_empty(walk_relpath))
328251881Speter    {
329251881Speter      /* Mergeinfo is explicit. */
330251881Speter      inherited = FALSE;
331251881Speter      *mergeinfo = wc_mergeinfo;
332251881Speter    }
333251881Speter  else
334251881Speter    {
335251881Speter      /* Mergeinfo may be inherited. */
336251881Speter      if (wc_mergeinfo)
337251881Speter        {
338251881Speter          inherited = TRUE;
339251881Speter          SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(mergeinfo,
340251881Speter                                                         wc_mergeinfo,
341251881Speter                                                         walk_relpath,
342251881Speter                                                         result_pool,
343251881Speter                                                         scratch_pool));
344251881Speter        }
345251881Speter      else
346251881Speter        {
347251881Speter          inherited = FALSE;
348251881Speter          *mergeinfo = NULL;
349251881Speter        }
350251881Speter    }
351251881Speter
352251881Speter  if (walked_path)
353251881Speter    *walked_path = walk_relpath;
354251881Speter
355251881Speter  /* Remove non-inheritable mergeinfo and paths mapped to empty ranges
356251881Speter     which may occur if WCPATH's mergeinfo is not explicit. */
357251881Speter  if (inherited
358251881Speter      && apr_hash_count(*mergeinfo)) /* Nothing to do for empty mergeinfo. */
359251881Speter    {
360251881Speter      SVN_ERR(svn_mergeinfo_inheritable2(mergeinfo, *mergeinfo, NULL,
361251881Speter                                         SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
362251881Speter                                         TRUE, result_pool, scratch_pool));
363289180Speter      svn_mergeinfo__remove_empty_rangelists(*mergeinfo, scratch_pool);
364251881Speter    }
365251881Speter
366251881Speter  if (inherited_p)
367251881Speter    *inherited_p = inherited;
368251881Speter
369251881Speter  return SVN_NO_ERROR;
370251881Speter}
371251881Speter
372251881Spetersvn_error_t *
373251881Spetersvn_client__get_wc_mergeinfo_catalog(svn_mergeinfo_catalog_t *mergeinfo_cat,
374251881Speter                                     svn_boolean_t *inherited,
375251881Speter                                     svn_boolean_t include_descendants,
376251881Speter                                     svn_mergeinfo_inheritance_t inherit,
377251881Speter                                     const char *local_abspath,
378251881Speter                                     const char *limit_path,
379251881Speter                                     const char **walked_path,
380251881Speter                                     svn_boolean_t ignore_invalid_mergeinfo,
381251881Speter                                     svn_client_ctx_t *ctx,
382251881Speter                                     apr_pool_t *result_pool,
383251881Speter                                     apr_pool_t *scratch_pool)
384251881Speter{
385251881Speter  const char *target_repos_relpath;
386251881Speter  svn_mergeinfo_t mergeinfo;
387251881Speter  const char *repos_root;
388251881Speter
389251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
390251881Speter  *mergeinfo_cat = NULL;
391251881Speter  SVN_ERR(svn_wc__node_get_repos_info(NULL, &target_repos_relpath,
392251881Speter                                      &repos_root, NULL,
393251881Speter                                      ctx->wc_ctx, local_abspath,
394251881Speter                                      scratch_pool, scratch_pool));
395251881Speter
396251881Speter  /* Get the mergeinfo for the LOCAL_ABSPATH target and set *INHERITED and
397251881Speter     *WALKED_PATH. */
398251881Speter  SVN_ERR(svn_client__get_wc_mergeinfo(&mergeinfo, inherited, inherit,
399251881Speter                                       local_abspath, limit_path,
400251881Speter                                       walked_path, ignore_invalid_mergeinfo,
401251881Speter                                       ctx, result_pool, scratch_pool));
402251881Speter
403251881Speter  /* Add any explicit/inherited mergeinfo for LOCAL_ABSPATH to
404251881Speter     *MERGEINFO_CAT. */
405251881Speter  if (mergeinfo)
406251881Speter    {
407251881Speter      *mergeinfo_cat = apr_hash_make(result_pool);
408251881Speter      svn_hash_sets(*mergeinfo_cat,
409251881Speter                    apr_pstrdup(result_pool, target_repos_relpath), mergeinfo);
410251881Speter    }
411251881Speter
412251881Speter  /* If LOCAL_ABSPATH is a directory and we want the subtree mergeinfo too,
413251881Speter     then get it.
414251881Speter
415251881Speter     With WC-NG it is cheaper to do a single db transaction, than first
416251881Speter     looking if we really have a directory. */
417251881Speter  if (include_descendants)
418251881Speter    {
419251881Speter      apr_hash_t *mergeinfo_props;
420251881Speter      apr_hash_index_t *hi;
421251881Speter
422251881Speter      SVN_ERR(svn_wc__prop_retrieve_recursive(&mergeinfo_props,
423251881Speter                                              ctx->wc_ctx, local_abspath,
424251881Speter                                              SVN_PROP_MERGEINFO,
425251881Speter                                              scratch_pool, scratch_pool));
426251881Speter
427251881Speter      /* Convert *mergeinfo_props into a proper svn_mergeinfo_catalog_t */
428251881Speter      for (hi = apr_hash_first(scratch_pool, mergeinfo_props);
429251881Speter           hi;
430251881Speter           hi = apr_hash_next(hi))
431251881Speter        {
432289180Speter          const char *node_abspath = apr_hash_this_key(hi);
433289180Speter          svn_string_t *propval = apr_hash_this_val(hi);
434251881Speter          svn_mergeinfo_t subtree_mergeinfo;
435251881Speter          const char *repos_relpath;
436251881Speter
437251881Speter          if (strcmp(node_abspath, local_abspath) == 0)
438251881Speter            continue; /* Already parsed in svn_client__get_wc_mergeinfo */
439251881Speter
440251881Speter          SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, NULL, NULL,
441251881Speter                                              ctx->wc_ctx, node_abspath,
442251881Speter                                              result_pool, scratch_pool));
443251881Speter
444251881Speter          SVN_ERR(svn_mergeinfo_parse(&subtree_mergeinfo, propval->data,
445251881Speter                                      result_pool));
446251881Speter
447251881Speter          /* If the target had no explicit/inherited mergeinfo and this is the
448251881Speter             first subtree with mergeinfo found, then the catalog will still
449251881Speter             be NULL. */
450251881Speter          if (*mergeinfo_cat == NULL)
451251881Speter            *mergeinfo_cat = apr_hash_make(result_pool);
452251881Speter
453251881Speter          svn_hash_sets(*mergeinfo_cat, repos_relpath, subtree_mergeinfo);
454251881Speter        }
455251881Speter    }
456251881Speter
457251881Speter  return SVN_NO_ERROR;
458251881Speter}
459251881Speter
460251881Spetersvn_error_t *
461251881Spetersvn_client__get_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo,
462251881Speter                                svn_ra_session_t *ra_session,
463251881Speter                                const char *url,
464251881Speter                                svn_revnum_t rev,
465251881Speter                                svn_mergeinfo_inheritance_t inherit,
466251881Speter                                svn_boolean_t squelch_incapable,
467251881Speter                                apr_pool_t *pool)
468251881Speter{
469251881Speter  svn_mergeinfo_catalog_t tgt_mergeinfo_cat;
470251881Speter
471251881Speter  *target_mergeinfo = NULL;
472251881Speter
473251881Speter  SVN_ERR(svn_client__get_repos_mergeinfo_catalog(&tgt_mergeinfo_cat,
474251881Speter                                                  ra_session,
475251881Speter                                                  url, rev, inherit,
476251881Speter                                                  squelch_incapable, FALSE,
477251881Speter                                                  pool, pool));
478251881Speter
479251881Speter  if (tgt_mergeinfo_cat && apr_hash_count(tgt_mergeinfo_cat))
480251881Speter    {
481251881Speter      /* We asked only for the REL_PATH's mergeinfo, not any of its
482251881Speter         descendants.  So if there is anything in the catalog it is the
483251881Speter         mergeinfo for REL_PATH. */
484251881Speter      *target_mergeinfo =
485289180Speter        apr_hash_this_val(apr_hash_first(pool, tgt_mergeinfo_cat));
486251881Speter
487251881Speter    }
488251881Speter
489251881Speter  return SVN_NO_ERROR;
490251881Speter}
491251881Speter
492251881Spetersvn_error_t *
493251881Spetersvn_client__get_repos_mergeinfo_catalog(svn_mergeinfo_catalog_t *mergeinfo_cat,
494251881Speter                                        svn_ra_session_t *ra_session,
495251881Speter                                        const char *url,
496251881Speter                                        svn_revnum_t rev,
497251881Speter                                        svn_mergeinfo_inheritance_t inherit,
498251881Speter                                        svn_boolean_t squelch_incapable,
499251881Speter                                        svn_boolean_t include_descendants,
500251881Speter                                        apr_pool_t *result_pool,
501251881Speter                                        apr_pool_t *scratch_pool)
502251881Speter{
503251881Speter  svn_error_t *err;
504251881Speter  svn_mergeinfo_catalog_t repos_mergeinfo_cat;
505251881Speter  apr_array_header_t *rel_paths = apr_array_make(scratch_pool, 1,
506251881Speter                                                 sizeof(const char *));
507251881Speter  const char *old_session_url;
508251881Speter
509251881Speter  APR_ARRAY_PUSH(rel_paths, const char *) = "";
510251881Speter
511251881Speter  /* Fetch the mergeinfo. */
512251881Speter  SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url,
513251881Speter                                            ra_session, url, scratch_pool));
514251881Speter  err = svn_ra_get_mergeinfo(ra_session, &repos_mergeinfo_cat, rel_paths,
515251881Speter                             rev, inherit, include_descendants, result_pool);
516251881Speter  err = svn_error_compose_create(
517251881Speter          err, svn_ra_reparent(ra_session, old_session_url, scratch_pool));
518251881Speter  if (err)
519251881Speter    {
520251881Speter      if (squelch_incapable && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
521251881Speter        {
522251881Speter          svn_error_clear(err);
523251881Speter          *mergeinfo_cat = NULL;
524251881Speter          return SVN_NO_ERROR;
525251881Speter        }
526251881Speter      else
527251881Speter        return svn_error_trace(err);
528251881Speter    }
529251881Speter
530251881Speter  if (repos_mergeinfo_cat == NULL)
531251881Speter    {
532251881Speter      *mergeinfo_cat = NULL;
533251881Speter    }
534251881Speter  else
535251881Speter    {
536251881Speter      const char *session_relpath;
537251881Speter
538251881Speter      SVN_ERR(svn_ra_get_path_relative_to_root(ra_session, &session_relpath,
539251881Speter                                               url, scratch_pool));
540251881Speter
541251881Speter      if (session_relpath[0] == '\0')
542251881Speter        *mergeinfo_cat = repos_mergeinfo_cat;
543251881Speter      else
544251881Speter        SVN_ERR(svn_mergeinfo__add_prefix_to_catalog(mergeinfo_cat,
545251881Speter                                                     repos_mergeinfo_cat,
546251881Speter                                                     session_relpath,
547251881Speter                                                     result_pool,
548251881Speter                                                     scratch_pool));
549251881Speter    }
550251881Speter  return SVN_NO_ERROR;
551251881Speter}
552251881Speter
553251881Speter
554251881Spetersvn_error_t *
555251881Spetersvn_client__get_wc_or_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo,
556251881Speter                                      svn_boolean_t *inherited,
557251881Speter                                      svn_boolean_t *from_repos,
558251881Speter                                      svn_boolean_t repos_only,
559251881Speter                                      svn_mergeinfo_inheritance_t inherit,
560251881Speter                                      svn_ra_session_t *ra_session,
561251881Speter                                      const char *target_wcpath,
562251881Speter                                      svn_client_ctx_t *ctx,
563251881Speter                                      apr_pool_t *pool)
564251881Speter{
565251881Speter  svn_mergeinfo_catalog_t tgt_mergeinfo_cat;
566251881Speter
567251881Speter  *target_mergeinfo = NULL;
568251881Speter
569251881Speter  SVN_ERR(svn_client__get_wc_or_repos_mergeinfo_catalog(&tgt_mergeinfo_cat,
570251881Speter                                                        inherited, from_repos,
571251881Speter                                                        FALSE,
572251881Speter                                                        repos_only,
573251881Speter                                                        FALSE, inherit,
574251881Speter                                                        ra_session,
575251881Speter                                                        target_wcpath, ctx,
576251881Speter                                                        pool, pool));
577251881Speter  if (tgt_mergeinfo_cat && apr_hash_count(tgt_mergeinfo_cat))
578251881Speter    {
579251881Speter      /* We asked only for the TARGET_WCPATH's mergeinfo, not any of its
580251881Speter         descendants.  It this mergeinfo is in the catalog, it's keyed
581251881Speter         on TARGET_WCPATH's root-relative path.  We could dig that up
582251881Speter         so we can peek into our catalog, but it ought to be the only
583251881Speter         thing in the catalog, so we'll just fetch the first hash item. */
584251881Speter      *target_mergeinfo =
585289180Speter        apr_hash_this_val(apr_hash_first(pool, tgt_mergeinfo_cat));
586251881Speter
587251881Speter    }
588251881Speter
589251881Speter  return SVN_NO_ERROR;
590251881Speter}
591251881Speter
592251881Spetersvn_error_t *
593251881Spetersvn_client__get_wc_or_repos_mergeinfo_catalog(
594251881Speter  svn_mergeinfo_catalog_t *target_mergeinfo_catalog,
595251881Speter  svn_boolean_t *inherited_p,
596251881Speter  svn_boolean_t *from_repos,
597251881Speter  svn_boolean_t include_descendants,
598251881Speter  svn_boolean_t repos_only,
599251881Speter  svn_boolean_t ignore_invalid_mergeinfo,
600251881Speter  svn_mergeinfo_inheritance_t inherit,
601251881Speter  svn_ra_session_t *ra_session,
602251881Speter  const char *target_wcpath,
603251881Speter  svn_client_ctx_t *ctx,
604251881Speter  apr_pool_t *result_pool,
605251881Speter  apr_pool_t *scratch_pool)
606251881Speter{
607251881Speter  const char *url;
608251881Speter  svn_revnum_t target_rev;
609251881Speter  const char *local_abspath;
610251881Speter  const char *repos_root;
611251881Speter  const char *repos_relpath;
612251881Speter  svn_mergeinfo_catalog_t target_mergeinfo_cat_wc = NULL;
613251881Speter  svn_mergeinfo_catalog_t target_mergeinfo_cat_repos = NULL;
614251881Speter
615251881Speter  SVN_ERR(svn_dirent_get_absolute(&local_abspath, target_wcpath,
616251881Speter                                  scratch_pool));
617251881Speter
618251881Speter  if (from_repos)
619251881Speter    *from_repos = FALSE;
620251881Speter
621251881Speter  /* We may get an entry with abbreviated information from TARGET_WCPATH's
622251881Speter     parent if TARGET_WCPATH is missing.  These limited entries do not have
623251881Speter     a URL and without that we cannot get accurate mergeinfo for
624251881Speter     TARGET_WCPATH. */
625251881Speter  SVN_ERR(svn_wc__node_get_origin(NULL, &target_rev, &repos_relpath,
626289180Speter                                  &repos_root, NULL, NULL, NULL,
627251881Speter                                  ctx->wc_ctx, local_abspath, FALSE,
628251881Speter                                  scratch_pool, scratch_pool));
629251881Speter
630251881Speter  if (repos_relpath)
631251881Speter    url = svn_path_url_add_component2(repos_root, repos_relpath, scratch_pool);
632251881Speter  else
633251881Speter    url = NULL;
634251881Speter
635251881Speter  if (!repos_only)
636251881Speter    {
637251881Speter      svn_boolean_t inherited;
638251881Speter      SVN_ERR(svn_client__get_wc_mergeinfo_catalog(&target_mergeinfo_cat_wc,
639251881Speter                                                   &inherited,
640251881Speter                                                   include_descendants,
641251881Speter                                                   inherit,
642251881Speter                                                   local_abspath,
643251881Speter                                                   NULL, NULL,
644251881Speter                                                   ignore_invalid_mergeinfo,
645251881Speter                                                   ctx,
646251881Speter                                                   result_pool,
647251881Speter                                                   scratch_pool));
648251881Speter      if (inherited_p)
649251881Speter        *inherited_p = inherited;
650251881Speter
651251881Speter      /* If we want LOCAL_ABSPATH's inherited mergeinfo, were we able to
652251881Speter         get it from the working copy?  If not, then we must ask the
653251881Speter         repository. */
654251881Speter      if (! (inherited
655251881Speter             || (inherit == svn_mergeinfo_explicit)
656251881Speter             || (repos_relpath
657251881Speter                 && target_mergeinfo_cat_wc
658251881Speter                 && svn_hash_gets(target_mergeinfo_cat_wc, repos_relpath))))
659251881Speter        {
660251881Speter          repos_only = TRUE;
661251881Speter          /* We already have any subtree mergeinfo from the working copy, no
662251881Speter             need to ask the server for it again. */
663251881Speter          include_descendants = FALSE;
664251881Speter        }
665251881Speter    }
666251881Speter
667251881Speter  if (repos_only)
668251881Speter    {
669251881Speter      /* No need to check the repos if this is a local addition. */
670251881Speter      if (url != NULL)
671251881Speter        {
672251881Speter          apr_hash_t *original_props;
673251881Speter
674251881Speter          /* Check to see if we have local modifications which removed all of
675251881Speter             TARGET_WCPATH's pristine mergeinfo.  If that is the case then
676251881Speter             TARGET_WCPATH effectively has no mergeinfo. */
677251881Speter          SVN_ERR(svn_wc_get_pristine_props(&original_props,
678251881Speter                                            ctx->wc_ctx, local_abspath,
679251881Speter                                            result_pool, scratch_pool));
680251881Speter          if (!svn_hash_gets(original_props, SVN_PROP_MERGEINFO))
681251881Speter            {
682251881Speter              apr_pool_t *sesspool = NULL;
683251881Speter
684251881Speter              if (! ra_session)
685251881Speter                {
686251881Speter                  sesspool = svn_pool_create(scratch_pool);
687251881Speter                  SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
688251881Speter                                                      ctx,
689251881Speter                                                      sesspool, sesspool));
690251881Speter                }
691251881Speter
692251881Speter              SVN_ERR(svn_client__get_repos_mergeinfo_catalog(
693251881Speter                        &target_mergeinfo_cat_repos, ra_session,
694251881Speter                        url, target_rev, inherit,
695251881Speter                        TRUE, include_descendants,
696251881Speter                        result_pool, scratch_pool));
697251881Speter
698251881Speter              if (target_mergeinfo_cat_repos
699251881Speter                  && svn_hash_gets(target_mergeinfo_cat_repos, repos_relpath))
700251881Speter                {
701251881Speter                  if (inherited_p)
702251881Speter                    *inherited_p = TRUE;
703251881Speter                  if (from_repos)
704251881Speter                    *from_repos = TRUE;
705251881Speter                }
706251881Speter
707251881Speter              /* If we created an RA_SESSION above, destroy it.
708251881Speter                 Otherwise, if reparented an existing session, point
709251881Speter                 it back where it was when we were called. */
710251881Speter              if (sesspool)
711251881Speter                {
712251881Speter                  svn_pool_destroy(sesspool);
713251881Speter                }
714251881Speter            }
715251881Speter        }
716251881Speter    }
717251881Speter
718251881Speter  /* Combine the mergeinfo from the working copy and repository as needed. */
719251881Speter  if (target_mergeinfo_cat_wc)
720251881Speter    {
721251881Speter      *target_mergeinfo_catalog = target_mergeinfo_cat_wc;
722251881Speter      if (target_mergeinfo_cat_repos)
723251881Speter        SVN_ERR(svn_mergeinfo_catalog_merge(*target_mergeinfo_catalog,
724251881Speter                                            target_mergeinfo_cat_repos,
725251881Speter                                            result_pool, scratch_pool));
726251881Speter    }
727251881Speter  else if (target_mergeinfo_cat_repos)
728251881Speter    {
729251881Speter      *target_mergeinfo_catalog = target_mergeinfo_cat_repos;
730251881Speter    }
731251881Speter  else
732251881Speter    {
733251881Speter      *target_mergeinfo_catalog = NULL;
734251881Speter    }
735251881Speter
736251881Speter  return SVN_NO_ERROR;
737251881Speter}
738251881Speter
739251881Speter
740251881Spetersvn_error_t *
741251881Spetersvn_client__get_history_as_mergeinfo(svn_mergeinfo_t *mergeinfo_p,
742251881Speter                                      svn_boolean_t *has_rev_zero_history,
743251881Speter                                      const svn_client__pathrev_t *pathrev,
744251881Speter                                      svn_revnum_t range_youngest,
745251881Speter                                      svn_revnum_t range_oldest,
746251881Speter                                      svn_ra_session_t *ra_session,
747251881Speter                                      svn_client_ctx_t *ctx,
748251881Speter                                      apr_pool_t *pool)
749251881Speter{
750251881Speter  apr_array_header_t *segments;
751251881Speter
752251881Speter  /* Fetch the location segments for our URL@PEG_REVNUM. */
753251881Speter  if (! SVN_IS_VALID_REVNUM(range_youngest))
754251881Speter    range_youngest = pathrev->rev;
755251881Speter  if (! SVN_IS_VALID_REVNUM(range_oldest))
756251881Speter    range_oldest = 0;
757251881Speter
758251881Speter  SVN_ERR(svn_client__repos_location_segments(&segments, ra_session,
759251881Speter                                              pathrev->url, pathrev->rev,
760251881Speter                                              range_youngest, range_oldest,
761251881Speter                                              ctx, pool));
762251881Speter
763251881Speter  if (has_rev_zero_history)
764251881Speter    {
765251881Speter      *has_rev_zero_history = FALSE;
766251881Speter        if (segments->nelts)
767251881Speter          {
768251881Speter            svn_location_segment_t *oldest_segment =
769251881Speter              APR_ARRAY_IDX(segments, 0, svn_location_segment_t *);
770251881Speter            if (oldest_segment->range_start == 0)
771251881Speter              *has_rev_zero_history = TRUE;
772251881Speter          }
773251881Speter    }
774251881Speter
775251881Speter  SVN_ERR(svn_mergeinfo__mergeinfo_from_segments(mergeinfo_p, segments, pool));
776251881Speter
777251881Speter  return SVN_NO_ERROR;
778251881Speter}
779251881Speter
780251881Speter
781251881Speter/*-----------------------------------------------------------------------*/
782251881Speter
783251881Speter/*** Eliding mergeinfo. ***/
784251881Speter
785251881Speter/* Given the mergeinfo (CHILD_MERGEINFO) for a path, and the
786251881Speter   mergeinfo of its nearest ancestor with mergeinfo (PARENT_MERGEINFO), compare
787251881Speter   CHILD_MERGEINFO to PARENT_MERGEINFO to see if the former elides to
788251881Speter   the latter, following the elision rules described in
789251881Speter   svn_client__elide_mergeinfo()'s docstring.  Set *ELIDES to whether
790251881Speter   or not CHILD_MERGEINFO is redundant.
791251881Speter
792251881Speter   Note: This function assumes that PARENT_MERGEINFO is definitive;
793251881Speter   i.e. if it is NULL then the caller not only walked the entire WC
794251881Speter   looking for inherited mergeinfo, but queried the repository if none
795251881Speter   was found in the WC.  This is rather important since this function
796251881Speter   says empty mergeinfo should be elided if PARENT_MERGEINFO is NULL,
797251881Speter   and we don't want to do that unless we are *certain* that the empty
798251881Speter   mergeinfo on PATH isn't overriding anything.
799251881Speter
800251881Speter   If PATH_SUFFIX and PARENT_MERGEINFO are not NULL append PATH_SUFFIX
801251881Speter   to each path in PARENT_MERGEINFO before performing the comparison. */
802251881Speterstatic svn_error_t *
803251881Spetershould_elide_mergeinfo(svn_boolean_t *elides,
804251881Speter                       svn_mergeinfo_t parent_mergeinfo,
805251881Speter                       svn_mergeinfo_t child_mergeinfo,
806251881Speter                       const char *path_suffix,
807251881Speter                       apr_pool_t *scratch_pool)
808251881Speter{
809251881Speter  /* Easy out: No child mergeinfo to elide. */
810251881Speter  if (child_mergeinfo == NULL)
811251881Speter    {
812251881Speter      *elides = FALSE;
813251881Speter    }
814251881Speter  else if (apr_hash_count(child_mergeinfo) == 0)
815251881Speter    {
816251881Speter      /* Empty mergeinfo elides to empty mergeinfo or to "nothing",
817251881Speter         i.e. it isn't overriding any parent. Otherwise it doesn't
818251881Speter         elide. */
819251881Speter      *elides = (!parent_mergeinfo || apr_hash_count(parent_mergeinfo) == 0);
820251881Speter    }
821251881Speter  else if (!parent_mergeinfo || apr_hash_count(parent_mergeinfo) == 0)
822251881Speter    {
823251881Speter      /* Non-empty mergeinfo never elides to empty mergeinfo
824251881Speter         or no mergeinfo. */
825251881Speter      *elides = FALSE;
826251881Speter    }
827251881Speter  else
828251881Speter    {
829251881Speter      /* Both CHILD_MERGEINFO and PARENT_MERGEINFO are non-NULL and
830251881Speter         non-empty. */
831251881Speter      svn_mergeinfo_t path_tweaked_parent_mergeinfo;
832251881Speter
833251881Speter      /* If we need to adjust the paths in PARENT_MERGEINFO do it now. */
834251881Speter      if (path_suffix)
835251881Speter        SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
836251881Speter                  &path_tweaked_parent_mergeinfo, parent_mergeinfo,
837251881Speter                  path_suffix, scratch_pool, scratch_pool));
838251881Speter      else
839251881Speter        path_tweaked_parent_mergeinfo = parent_mergeinfo;
840251881Speter
841251881Speter      SVN_ERR(svn_mergeinfo__equals(elides,
842251881Speter                                    path_tweaked_parent_mergeinfo,
843251881Speter                                    child_mergeinfo, TRUE, scratch_pool));
844251881Speter    }
845251881Speter
846251881Speter  return SVN_NO_ERROR;
847251881Speter}
848251881Speter
849251881Speter/* Helper for svn_client__elide_mergeinfo().
850251881Speter
851251881Speter   Given a working copy LOCAL_ABSPATH, its mergeinfo hash CHILD_MERGEINFO, and
852251881Speter   the mergeinfo of LOCAL_ABSPATH's nearest ancestor PARENT_MERGEINFO, use
853251881Speter   should_elide_mergeinfo() to decide whether or not CHILD_MERGEINFO elides to
854251881Speter   PARENT_MERGEINFO; PATH_SUFFIX means the same as in that function.
855251881Speter
856251881Speter   If elision does occur, then remove the mergeinfo for LOCAL_ABSPATH.
857251881Speter
858251881Speter   If CHILD_MERGEINFO is NULL, do nothing.
859251881Speter
860251881Speter   Use SCRATCH_POOL for temporary allocations.
861251881Speter*/
862251881Speterstatic svn_error_t *
863251881Speterelide_mergeinfo(svn_mergeinfo_t parent_mergeinfo,
864251881Speter                svn_mergeinfo_t child_mergeinfo,
865251881Speter                const char *local_abspath,
866251881Speter                svn_client_ctx_t *ctx,
867251881Speter                apr_pool_t *scratch_pool)
868251881Speter{
869251881Speter  svn_boolean_t elides;
870251881Speter
871251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
872251881Speter
873251881Speter  SVN_ERR(should_elide_mergeinfo(&elides,
874251881Speter                                 parent_mergeinfo, child_mergeinfo, NULL,
875251881Speter                                 scratch_pool));
876251881Speter
877251881Speter  if (elides)
878251881Speter    {
879251881Speter      SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, SVN_PROP_MERGEINFO,
880251881Speter                               NULL, svn_depth_empty, TRUE, NULL,
881251881Speter                               NULL, NULL /* cancellation */,
882251881Speter                               NULL, NULL /* notification */,
883251881Speter                               scratch_pool));
884251881Speter
885251881Speter      if (ctx->notify_func2)
886251881Speter        {
887251881Speter          svn_wc_notify_t *notify;
888251881Speter
889251881Speter          notify = svn_wc_create_notify(local_abspath,
890251881Speter                                        svn_wc_notify_merge_elide_info,
891251881Speter                                        scratch_pool);
892251881Speter          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
893251881Speter
894251881Speter          notify = svn_wc_create_notify(local_abspath,
895251881Speter                                        svn_wc_notify_update_update,
896251881Speter                                        scratch_pool);
897251881Speter          notify->prop_state = svn_wc_notify_state_changed;
898251881Speter
899251881Speter          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
900251881Speter        }
901251881Speter    }
902251881Speter
903251881Speter  return SVN_NO_ERROR;
904251881Speter}
905251881Speter
906251881Speter
907251881Spetersvn_error_t *
908251881Spetersvn_client__elide_mergeinfo(const char *target_abspath,
909251881Speter                            const char *wc_elision_limit_abspath,
910251881Speter                            svn_client_ctx_t *ctx,
911251881Speter                            apr_pool_t *pool)
912251881Speter{
913251881Speter  const char *limit_abspath = wc_elision_limit_abspath;
914251881Speter
915251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
916251881Speter  SVN_ERR_ASSERT(!wc_elision_limit_abspath || svn_dirent_is_absolute(wc_elision_limit_abspath));
917251881Speter
918251881Speter  /* Check for first easy out: We are already at the limit path. */
919251881Speter  if (!limit_abspath
920251881Speter      || strcmp(target_abspath, limit_abspath) != 0)
921251881Speter    {
922251881Speter      svn_mergeinfo_t target_mergeinfo;
923251881Speter      svn_mergeinfo_t mergeinfo = NULL;
924251881Speter      svn_error_t *err;
925251881Speter
926251881Speter      /* Get the TARGET_WCPATH's explicit mergeinfo. */
927289180Speter      err = svn_client__get_wc_mergeinfo(&target_mergeinfo, NULL,
928289180Speter                                         svn_mergeinfo_explicit,
929251881Speter                                         target_abspath,
930289180Speter                                         NULL, NULL, FALSE,
931251881Speter                                         ctx, pool, pool);
932251881Speter      if (err)
933251881Speter        {
934251881Speter          if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
935251881Speter            {
936251881Speter              /* Issue #3896: If we attempt elision because invalid
937251881Speter                 mergeinfo is present on TARGET_WCPATH, then don't let
938251881Speter                 the merge fail, just skip the elision attempt. */
939251881Speter              svn_error_clear(err);
940251881Speter              return SVN_NO_ERROR;
941251881Speter            }
942251881Speter          else
943251881Speter            {
944251881Speter              return svn_error_trace(err);
945251881Speter            }
946251881Speter        }
947251881Speter
948251881Speter     /* If TARGET_WCPATH has no explicit mergeinfo, there's nothing to
949251881Speter         elide, we're done. */
950289180Speter      if (target_mergeinfo == NULL)
951251881Speter        return SVN_NO_ERROR;
952251881Speter
953251881Speter      /* Get TARGET_WCPATH's inherited mergeinfo from the WC. */
954251881Speter      err = svn_client__get_wc_mergeinfo(&mergeinfo, NULL,
955251881Speter                                         svn_mergeinfo_nearest_ancestor,
956251881Speter                                         target_abspath,
957251881Speter                                         limit_abspath,
958289180Speter                                         NULL, FALSE, ctx, pool, pool);
959251881Speter      if (err)
960251881Speter        {
961251881Speter          if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
962251881Speter            {
963251881Speter              /* Issue #3896 again, but invalid mergeinfo is inherited. */
964251881Speter              svn_error_clear(err);
965251881Speter              return SVN_NO_ERROR;
966251881Speter            }
967251881Speter          else
968251881Speter            {
969251881Speter              return svn_error_trace(err);
970251881Speter            }
971251881Speter        }
972251881Speter
973251881Speter      /* If TARGET_WCPATH inherited no mergeinfo from the WC and we are
974251881Speter         not limiting our search to the working copy then check if it
975251881Speter         inherits any from the repos. */
976251881Speter      if (!mergeinfo && !wc_elision_limit_abspath)
977251881Speter        {
978251881Speter          err = svn_client__get_wc_or_repos_mergeinfo(
979251881Speter            &mergeinfo, NULL, NULL, TRUE,
980251881Speter            svn_mergeinfo_nearest_ancestor,
981251881Speter            NULL, target_abspath, ctx, pool);
982251881Speter          if (err)
983251881Speter            {
984251881Speter              if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
985251881Speter                {
986251881Speter                  /* Issue #3896 again, but invalid mergeinfo is inherited
987251881Speter                     from the repository. */
988251881Speter                  svn_error_clear(err);
989251881Speter                  return SVN_NO_ERROR;
990251881Speter                }
991251881Speter              else
992251881Speter                {
993251881Speter                  return svn_error_trace(err);
994251881Speter                }
995251881Speter            }
996251881Speter        }
997251881Speter
998251881Speter      /* If there is nowhere to elide TARGET_WCPATH's mergeinfo to and
999251881Speter         the elision is limited, then we are done.*/
1000251881Speter      if (!mergeinfo && wc_elision_limit_abspath)
1001251881Speter        return SVN_NO_ERROR;
1002251881Speter
1003251881Speter      SVN_ERR(elide_mergeinfo(mergeinfo, target_mergeinfo, target_abspath,
1004251881Speter                              ctx, pool));
1005251881Speter    }
1006251881Speter  return SVN_NO_ERROR;
1007251881Speter}
1008251881Speter
1009251881Speter
1010251881Speter/* Set *MERGEINFO_CATALOG to the explicit or inherited mergeinfo for
1011251881Speter   PATH_OR_URL@PEG_REVISION.  If INCLUDE_DESCENDANTS is true, also
1012251881Speter   store in *MERGEINFO_CATALOG the explicit mergeinfo on any subtrees
1013251881Speter   under PATH_OR_URL.  Key all mergeinfo in *MERGEINFO_CATALOG on
1014251881Speter   repository relpaths.
1015251881Speter
1016251881Speter   If no mergeinfo is found then set *MERGEINFO_CATALOG to NULL.
1017251881Speter
1018251881Speter   Set *REPOS_ROOT to the root URL of the repository associated with
1019251881Speter   PATH_OR_URL.
1020251881Speter
1021257936Speter   If RA_SESSION is NOT NULL and PATH_OR_URL refers to a URL, RA_SESSION
1022257936Speter   (which must be of the repository containing PATH_OR_URL) will be used
1023257936Speter   instead of a temporary RA session. Caller is responsible for reparenting
1024257936Speter   the session if it wants to use it after the call.
1025257936Speter
1026251881Speter   Allocate *MERGEINFO_CATALOG and all its contents in RESULT_POOL.  Use
1027251881Speter   SCRATCH_POOL for all temporary allocations.
1028251881Speter
1029251881Speter   Return SVN_ERR_UNSUPPORTED_FEATURE if the server does not support
1030251881Speter   Merge Tracking.  */
1031251881Speterstatic svn_error_t *
1032251881Speterget_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo_catalog,
1033251881Speter              const char **repos_root,
1034251881Speter              const char *path_or_url,
1035251881Speter              const svn_opt_revision_t *peg_revision,
1036251881Speter              svn_boolean_t include_descendants,
1037251881Speter              svn_boolean_t ignore_invalid_mergeinfo,
1038251881Speter              svn_client_ctx_t *ctx,
1039257936Speter              svn_ra_session_t *ra_session,
1040251881Speter              apr_pool_t *result_pool,
1041251881Speter              apr_pool_t *scratch_pool)
1042251881Speter{
1043251881Speter  const char *local_abspath;
1044251881Speter  svn_boolean_t use_url = svn_path_is_url(path_or_url);
1045251881Speter  svn_client__pathrev_t *peg_loc;
1046251881Speter
1047257936Speter  if (ra_session && svn_path_is_url(path_or_url))
1048257936Speter    {
1049257936Speter      SVN_ERR(svn_ra_reparent(ra_session, path_or_url, scratch_pool));
1050257936Speter      SVN_ERR(svn_client__resolve_rev_and_url(&peg_loc, ra_session,
1051257936Speter                                              path_or_url,
1052257936Speter                                              peg_revision,
1053257936Speter                                              peg_revision,
1054257936Speter                                              ctx, scratch_pool));
1055257936Speter    }
1056257936Speter  else
1057257936Speter    {
1058257936Speter      SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &peg_loc,
1059257936Speter                                                path_or_url, NULL,
1060257936Speter                                                peg_revision,
1061257936Speter                                                peg_revision, ctx, scratch_pool));
1062257936Speter    }
1063251881Speter
1064251881Speter  /* If PATH_OR_URL is as working copy path determine if we will need to
1065251881Speter     contact the repository for the requested PEG_REVISION. */
1066251881Speter  if (!use_url)
1067251881Speter    {
1068251881Speter      svn_client__pathrev_t *origin;
1069251881Speter      SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
1070251881Speter                                      scratch_pool));
1071251881Speter
1072251881Speter      SVN_ERR(svn_client__wc_node_get_origin(&origin, local_abspath, ctx,
1073251881Speter                                             scratch_pool, scratch_pool));
1074251881Speter      if (!origin
1075251881Speter          || strcmp(origin->url, peg_loc->url) != 0
1076251881Speter          || peg_loc->rev != origin->rev)
1077251881Speter      {
1078251881Speter        use_url = TRUE; /* Don't rely on local mergeinfo */
1079251881Speter      }
1080251881Speter    }
1081251881Speter
1082251881Speter  SVN_ERR(svn_ra_get_repos_root2(ra_session, repos_root, result_pool));
1083251881Speter
1084251881Speter  if (use_url)
1085251881Speter    {
1086251881Speter      SVN_ERR(svn_client__get_repos_mergeinfo_catalog(
1087251881Speter        mergeinfo_catalog, ra_session, peg_loc->url, peg_loc->rev,
1088251881Speter        svn_mergeinfo_inherited, FALSE, include_descendants,
1089251881Speter        result_pool, scratch_pool));
1090251881Speter    }
1091251881Speter  else /* ! svn_path_is_url() */
1092251881Speter    {
1093251881Speter      SVN_ERR(svn_client__get_wc_or_repos_mergeinfo_catalog(
1094251881Speter        mergeinfo_catalog, NULL, NULL, include_descendants, FALSE,
1095251881Speter        ignore_invalid_mergeinfo, svn_mergeinfo_inherited,
1096251881Speter        ra_session, path_or_url, ctx,
1097251881Speter        result_pool, scratch_pool));
1098251881Speter    }
1099251881Speter
1100251881Speter  return SVN_NO_ERROR;
1101251881Speter}
1102251881Speter
1103251881Speter/*** In-memory mergeinfo elision ***/
1104251881Spetersvn_error_t *
1105251881Spetersvn_client__elide_mergeinfo_catalog(svn_mergeinfo_catalog_t mergeinfo_catalog,
1106251881Speter                                    apr_pool_t *scratch_pool)
1107251881Speter{
1108251881Speter  apr_array_header_t *sorted_hash;
1109251881Speter  apr_array_header_t *elidable_paths = apr_array_make(scratch_pool, 1,
1110251881Speter                                                      sizeof(const char *));
1111251881Speter  apr_array_header_t *dir_stack = apr_array_make(scratch_pool, 1,
1112251881Speter                                                 sizeof(const char *));
1113251881Speter  apr_pool_t *iterpool;
1114251881Speter  int i;
1115251881Speter
1116251881Speter  /* Here's the general algorithm:
1117251881Speter     Walk through the paths sorted in tree order.  For each path, pop
1118251881Speter     the dir_stack until it is either empty or the top item contains a parent
1119251881Speter     of the current path. Check to see if that mergeinfo is then elidable,
1120251881Speter     and build the list of elidable mergeinfo based upon that determination.
1121251881Speter     Finally, push the path of interest onto the stack, and continue. */
1122251881Speter  sorted_hash = svn_sort__hash(mergeinfo_catalog,
1123251881Speter                               svn_sort_compare_items_as_paths,
1124251881Speter                               scratch_pool);
1125251881Speter  iterpool = svn_pool_create(scratch_pool);
1126251881Speter  for (i = 0; i < sorted_hash->nelts; i++)
1127251881Speter    {
1128251881Speter      svn_sort__item_t *item = &APR_ARRAY_IDX(sorted_hash, i,
1129251881Speter                                              svn_sort__item_t);
1130251881Speter      const char *path = item->key;
1131251881Speter
1132251881Speter      if (dir_stack->nelts > 0)
1133251881Speter        {
1134251881Speter          const char *top;
1135251881Speter          const char *path_suffix;
1136251881Speter          svn_boolean_t elides = FALSE;
1137251881Speter
1138251881Speter          svn_pool_clear(iterpool);
1139251881Speter
1140251881Speter          /* Pop off any paths which are not ancestors of PATH. */
1141251881Speter          do
1142251881Speter            {
1143251881Speter              top = APR_ARRAY_IDX(dir_stack, dir_stack->nelts - 1,
1144251881Speter                                          const char *);
1145251881Speter              path_suffix = svn_dirent_is_child(top, path, NULL);
1146251881Speter
1147251881Speter              if (!path_suffix)
1148251881Speter                apr_array_pop(dir_stack);
1149251881Speter            }
1150251881Speter          while (dir_stack->nelts > 0 && !path_suffix);
1151251881Speter
1152251881Speter          /* If we have a path suffix, it means we haven't popped the stack
1153251881Speter             clean. */
1154251881Speter          if (path_suffix)
1155251881Speter            {
1156251881Speter              SVN_ERR(should_elide_mergeinfo(&elides,
1157251881Speter                                         svn_hash_gets(mergeinfo_catalog, top),
1158251881Speter                                         svn_hash_gets(mergeinfo_catalog, path),
1159251881Speter                                         path_suffix,
1160251881Speter                                         iterpool));
1161251881Speter
1162251881Speter              if (elides)
1163251881Speter                APR_ARRAY_PUSH(elidable_paths, const char *) = path;
1164251881Speter            }
1165251881Speter        }
1166251881Speter
1167251881Speter      APR_ARRAY_PUSH(dir_stack, const char *) = path;
1168251881Speter    }
1169251881Speter  svn_pool_destroy(iterpool);
1170251881Speter
1171251881Speter  /* Now remove the elidable paths from the catalog. */
1172251881Speter  for (i = 0; i < elidable_paths->nelts; i++)
1173251881Speter    {
1174251881Speter      const char *path = APR_ARRAY_IDX(elidable_paths, i, const char *);
1175251881Speter      svn_hash_sets(mergeinfo_catalog, path, NULL);
1176251881Speter    }
1177251881Speter
1178251881Speter  return SVN_NO_ERROR;
1179251881Speter}
1180251881Speter
1181251881Speter
1182251881Speter/* Helper for filter_log_entry_with_rangelist().
1183251881Speter
1184251881Speter   DEPTH_FIRST_CATALOG_INDEX is an array of svn_sort__item_t's.  The keys are
1185251881Speter   repository-absolute const char *paths, the values are svn_mergeinfo_t for
1186251881Speter   each path.
1187251881Speter
1188251881Speter   Return a pointer to the mergeinfo value of the nearest path-wise ancestor
1189251881Speter   of FSPATH in DEPTH_FIRST_CATALOG_INDEX.  A path is considered its
1190251881Speter   own ancestor, so if a key exactly matches FSPATH, return that
1191251881Speter   key's mergeinfo and set *ANCESTOR_IS_SELF to true (set it to false in all
1192251881Speter   other cases).
1193251881Speter
1194251881Speter   If DEPTH_FIRST_CATALOG_INDEX is NULL, empty, or no ancestor is found, then
1195251881Speter   return NULL. */
1196251881Speterstatic svn_mergeinfo_t
1197251881Speterfind_nearest_ancestor(const apr_array_header_t *depth_first_catalog_index,
1198251881Speter                      svn_boolean_t *ancestor_is_self,
1199251881Speter                      const char *fspath)
1200251881Speter{
1201251881Speter  int ancestor_index = -1;
1202251881Speter
1203251881Speter  *ancestor_is_self = FALSE;
1204251881Speter
1205251881Speter  if (depth_first_catalog_index)
1206251881Speter    {
1207251881Speter      int i;
1208251881Speter
1209251881Speter      for (i = 0; i < depth_first_catalog_index->nelts; i++)
1210251881Speter        {
1211251881Speter          svn_sort__item_t item = APR_ARRAY_IDX(depth_first_catalog_index, i,
1212251881Speter                                                svn_sort__item_t);
1213251881Speter          if (svn_fspath__skip_ancestor(item.key, fspath))
1214251881Speter            {
1215251881Speter              ancestor_index = i;
1216251881Speter
1217251881Speter              /* There's no nearer ancestor than FSPATH itself. */
1218251881Speter              if (strcmp(item.key, fspath) == 0)
1219251881Speter                {
1220251881Speter                  *ancestor_is_self = TRUE;
1221251881Speter                  break;
1222251881Speter                }
1223251881Speter            }
1224251881Speter
1225251881Speter        }
1226251881Speter    }
1227251881Speter
1228251881Speter  if (ancestor_index == -1)
1229251881Speter    return NULL;
1230251881Speter  else
1231251881Speter    return (APR_ARRAY_IDX(depth_first_catalog_index,
1232251881Speter                          ancestor_index,
1233251881Speter                          svn_sort__item_t)).value;
1234251881Speter}
1235251881Speter
1236251881Speter/* Baton for use with the filter_log_entry_with_rangelist()
1237251881Speter   svn_log_entry_receiver_t callback. */
1238251881Speterstruct filter_log_entry_baton_t
1239251881Speter{
1240251881Speter  /* Is TRUE if RANGELIST describes potentially merged revisions, is FALSE
1241251881Speter     if RANGELIST describes potentially eligible revisions. */
1242251881Speter  svn_boolean_t filtering_merged;
1243251881Speter
1244251881Speter  /* Unsorted array of repository relative paths representing the merge
1245251881Speter     sources.  There will be more than one source  */
1246251881Speter  const apr_array_header_t *merge_source_fspaths;
1247251881Speter
1248251881Speter  /* The repository-absolute path we are calling svn_client_log5() on. */
1249251881Speter  const char *target_fspath;
1250251881Speter
1251251881Speter  /* Mergeinfo catalog for the tree rooted at TARGET_FSPATH.
1252251881Speter     The path keys must be repository-absolute. */
1253251881Speter  svn_mergeinfo_catalog_t target_mergeinfo_catalog;
1254251881Speter
1255251881Speter  /* Depth first sorted array of svn_sort__item_t's for
1256251881Speter     TARGET_MERGEINFO_CATALOG. */
1257251881Speter  apr_array_header_t *depth_first_catalog_index;
1258251881Speter
1259251881Speter  /* A rangelist describing all the revisions potentially merged or
1260251881Speter     potentially eligible for merging (see FILTERING_MERGED) based on
1261251881Speter     the target's explicit or inherited mergeinfo. */
1262251881Speter  const svn_rangelist_t *rangelist;
1263251881Speter
1264251881Speter  /* The wrapped svn_log_entry_receiver_t callback and baton which
1265251881Speter     filter_log_entry_with_rangelist() is acting as a filter for. */
1266251881Speter  svn_log_entry_receiver_t log_receiver;
1267251881Speter  void *log_receiver_baton;
1268251881Speter
1269251881Speter  svn_client_ctx_t *ctx;
1270251881Speter};
1271251881Speter
1272251881Speter/* Implements the svn_log_entry_receiver_t interface.  BATON is a
1273251881Speter   `struct filter_log_entry_baton_t *'.
1274251881Speter
1275251881Speter   Call the wrapped log receiver BATON->log_receiver (with
1276251881Speter   BATON->log_receiver_baton) if:
1277251881Speter
1278251881Speter   BATON->FILTERING_MERGED is FALSE and the changes represented by LOG_ENTRY
1279251881Speter   have been fully merged from BATON->merge_source_fspaths to the WC target
1280251881Speter   based on the mergeinfo for the WC contained in BATON->TARGET_MERGEINFO_CATALOG.
1281251881Speter
1282251881Speter   Or
1283251881Speter
1284251881Speter   BATON->FILTERING_MERGED is TRUE and the changes represented by LOG_ENTRY
1285251881Speter   have not been merged, or only partially merged, from
1286251881Speter   BATON->merge_source_fspaths to the WC target based on the mergeinfo for the
1287251881Speter   WC contained in BATON->TARGET_MERGEINFO_CATALOG. */
1288251881Speterstatic svn_error_t *
1289251881Speterfilter_log_entry_with_rangelist(void *baton,
1290251881Speter                                svn_log_entry_t *log_entry,
1291251881Speter                                apr_pool_t *pool)
1292251881Speter{
1293251881Speter  struct filter_log_entry_baton_t *fleb = baton;
1294251881Speter  svn_rangelist_t *intersection, *this_rangelist;
1295251881Speter
1296251881Speter  if (fleb->ctx->cancel_func)
1297251881Speter    SVN_ERR(fleb->ctx->cancel_func(fleb->ctx->cancel_baton));
1298251881Speter
1299251881Speter  /* Ignore r0 because there can be no "change 0" in a merge range. */
1300251881Speter  if (log_entry->revision == 0)
1301251881Speter    return SVN_NO_ERROR;
1302251881Speter
1303251881Speter  this_rangelist = svn_rangelist__initialize(log_entry->revision - 1,
1304251881Speter                                             log_entry->revision,
1305251881Speter                                             TRUE, pool);
1306251881Speter
1307251881Speter  /* Don't consider inheritance yet, see if LOG_ENTRY->REVISION is
1308251881Speter     fully or partially represented in BATON->RANGELIST. */
1309251881Speter  SVN_ERR(svn_rangelist_intersect(&intersection, fleb->rangelist,
1310251881Speter                                  this_rangelist, FALSE, pool));
1311251881Speter  if (! (intersection && intersection->nelts))
1312251881Speter    return SVN_NO_ERROR;
1313251881Speter
1314251881Speter  SVN_ERR_ASSERT(intersection->nelts == 1);
1315251881Speter
1316251881Speter  /* Ok, we know LOG_ENTRY->REVISION is represented in BATON->RANGELIST,
1317251881Speter     but is it only partially represented, i.e. is the corresponding range in
1318251881Speter     BATON->RANGELIST non-inheritable?  Ask for the same intersection as
1319251881Speter     above but consider inheritance this time, if the intersection is empty
1320251881Speter     we know the range in BATON->RANGELIST is non-inheritable. */
1321251881Speter  SVN_ERR(svn_rangelist_intersect(&intersection, fleb->rangelist,
1322251881Speter                                  this_rangelist, TRUE, pool));
1323251881Speter  log_entry->non_inheritable = !intersection->nelts;
1324251881Speter
1325251881Speter  /* If the paths changed by LOG_ENTRY->REVISION are provided we can determine
1326251881Speter     if LOG_ENTRY->REVISION, while only partially represented in
1327251881Speter     BATON->RANGELIST, is in fact completely applied to all affected paths.
1328251881Speter     ### And ... what if it is, or if it isn't? What do we do with the answer?
1329251881Speter         And how do we cope if the changed paths are not provided? */
1330251881Speter  if ((log_entry->non_inheritable || !fleb->filtering_merged)
1331251881Speter      && log_entry->changed_paths2)
1332251881Speter    {
1333251881Speter      apr_hash_index_t *hi;
1334251881Speter      svn_boolean_t all_subtrees_have_this_rev = TRUE;
1335251881Speter      svn_rangelist_t *this_rev_rangelist =
1336251881Speter        svn_rangelist__initialize(log_entry->revision - 1,
1337251881Speter                                  log_entry->revision, TRUE, pool);
1338251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
1339251881Speter
1340251881Speter      for (hi = apr_hash_first(pool, log_entry->changed_paths2);
1341251881Speter           hi;
1342251881Speter           hi = apr_hash_next(hi))
1343251881Speter        {
1344251881Speter          int i;
1345289180Speter          const char *path = apr_hash_this_key(hi);
1346289180Speter          svn_log_changed_path2_t *change = apr_hash_this_val(hi);
1347251881Speter          const char *target_fspath_affected;
1348251881Speter          svn_mergeinfo_t nearest_ancestor_mergeinfo;
1349251881Speter          svn_boolean_t found_this_revision = FALSE;
1350251881Speter          const char *merge_source_rel_target;
1351251881Speter          const char *merge_source_fspath;
1352251881Speter          svn_boolean_t ancestor_is_self;
1353251881Speter
1354251881Speter          svn_pool_clear(iterpool);
1355251881Speter
1356251881Speter          /* Check that PATH is a subtree of at least one of the
1357251881Speter             merge sources.  If not then ignore this path.  */
1358251881Speter          for (i = 0; i < fleb->merge_source_fspaths->nelts; i++)
1359251881Speter            {
1360251881Speter              merge_source_fspath = APR_ARRAY_IDX(fleb->merge_source_fspaths,
1361251881Speter                                                  i, const char *);
1362251881Speter
1363251881Speter              merge_source_rel_target
1364251881Speter                = svn_fspath__skip_ancestor(merge_source_fspath, path);
1365251881Speter              if (merge_source_rel_target)
1366251881Speter                {
1367251881Speter                  /* If MERGE_SOURCE was itself deleted, replaced, or added
1368251881Speter                     in LOG_ENTRY->REVISION then ignore this PATH since you
1369251881Speter                     can't merge a addition or deletion of yourself. */
1370251881Speter                  if (merge_source_rel_target[0] == '\0'
1371251881Speter                      && (change->action != 'M'))
1372251881Speter                    i = fleb->merge_source_fspaths->nelts;
1373251881Speter                  break;
1374251881Speter                }
1375251881Speter            }
1376251881Speter          /* If we examined every merge source path and PATH is a child of
1377251881Speter             none of them then we can ignore this PATH. */
1378251881Speter          if (i == fleb->merge_source_fspaths->nelts)
1379251881Speter            continue;
1380251881Speter
1381251881Speter          /* Calculate the target path which PATH would affect if merged. */
1382251881Speter          target_fspath_affected = svn_fspath__join(fleb->target_fspath,
1383251881Speter                                                    merge_source_rel_target,
1384251881Speter                                                    iterpool);
1385251881Speter
1386251881Speter          nearest_ancestor_mergeinfo =
1387251881Speter            find_nearest_ancestor(fleb->depth_first_catalog_index,
1388251881Speter                                  &ancestor_is_self,
1389251881Speter                                  target_fspath_affected);
1390251881Speter
1391251881Speter          /* Issue #3791: A path should never have explicit mergeinfo
1392251881Speter             describing its own addition (that's self-referential).  Nor will
1393251881Speter             it have explicit mergeinfo describing its own deletion (we
1394251881Speter             obviously can't add new mergeinfo to a path we are deleting).
1395251881Speter
1396251881Speter             This lack of explicit mergeinfo should not cause such revisions
1397251881Speter             to show up as eligible however.  If PATH was deleted, replaced,
1398251881Speter             or added in LOG_ENTRY->REVISION, but the corresponding
1399251881Speter             TARGET_PATH_AFFECTED already exists and has explicit mergeinfo
1400251881Speter             describing merges from PATH *after* LOG_ENTRY->REVISION, then
1401251881Speter             ignore this PATH.  If it was deleted in LOG_ENTRY->REVISION it's
1402251881Speter             obviously back.  If it was added or replaced it's still around
1403251881Speter             possibly it was replaced one or more times, but it's back now.
1404251881Speter             Regardless, LOG_ENTRY->REVISION is *not* an eligible revision! */
1405257936Speter          if (nearest_ancestor_mergeinfo &&
1406257936Speter              ancestor_is_self /* Explicit mergeinfo on TARGET_PATH_AFFECTED */
1407251881Speter              && (change->action != 'M'))
1408251881Speter            {
1409251881Speter              svn_rangelist_t *rangelist =
1410251881Speter                  svn_hash_gets(nearest_ancestor_mergeinfo, path);
1411257936Speter              if (rangelist)
1412257936Speter                {
1413257936Speter                  svn_merge_range_t *youngest_range = APR_ARRAY_IDX(
1414257936Speter                    rangelist, rangelist->nelts - 1, svn_merge_range_t *);
1415251881Speter
1416257936Speter                  if (youngest_range
1417257936Speter                      && (youngest_range->end > log_entry->revision))
1418257936Speter                    continue;
1419257936Speter                }
1420251881Speter            }
1421251881Speter
1422251881Speter          if (nearest_ancestor_mergeinfo)
1423251881Speter            {
1424251881Speter              apr_hash_index_t *hi2;
1425251881Speter
1426251881Speter              for (hi2 = apr_hash_first(iterpool, nearest_ancestor_mergeinfo);
1427251881Speter                   hi2;
1428251881Speter                   hi2 = apr_hash_next(hi2))
1429251881Speter                {
1430289180Speter                  const char *mergeinfo_path = apr_hash_this_key(hi2);
1431289180Speter                  svn_rangelist_t *rangelist = apr_hash_this_val(hi2);
1432251881Speter
1433251881Speter                  /* Does the mergeinfo for PATH reflect if
1434251881Speter                     LOG_ENTRY->REVISION was previously merged
1435251881Speter                     from MERGE_SOURCE_FSPATH? */
1436251881Speter                  if (svn_fspath__skip_ancestor(merge_source_fspath,
1437251881Speter                                                mergeinfo_path))
1438251881Speter                    {
1439251881Speter                      /* Something was merged from MERGE_SOURCE_FSPATH, does
1440251881Speter                         it include LOG_ENTRY->REVISION? */
1441251881Speter                      SVN_ERR(svn_rangelist_intersect(&intersection,
1442251881Speter                                                      rangelist,
1443251881Speter                                                      this_rev_rangelist,
1444251881Speter                                                      FALSE,
1445251881Speter                                                      iterpool));
1446251881Speter                      if (intersection->nelts)
1447251881Speter                        {
1448251881Speter                          if (ancestor_is_self)
1449251881Speter                            {
1450251881Speter                              /* TARGET_PATH_AFFECTED has explicit mergeinfo,
1451251881Speter                                 so we don't need to worry if that mergeinfo
1452251881Speter                                 is inheritable or not. */
1453251881Speter                              found_this_revision = TRUE;
1454251881Speter                              break;
1455251881Speter                            }
1456251881Speter                          else
1457251881Speter                            {
1458251881Speter                              /* TARGET_PATH_AFFECTED inherited its mergeinfo,
1459251881Speter                                 so we have to ignore non-inheritable
1460251881Speter                                 ranges. */
1461251881Speter                              SVN_ERR(svn_rangelist_intersect(
1462251881Speter                                &intersection,
1463251881Speter                                rangelist,
1464251881Speter                                this_rev_rangelist,
1465251881Speter                                TRUE, iterpool));
1466251881Speter                              if (intersection->nelts)
1467251881Speter                                {
1468251881Speter                                  found_this_revision = TRUE;
1469251881Speter                                  break;
1470251881Speter                                }
1471251881Speter                            }
1472251881Speter                        }
1473251881Speter                    }
1474251881Speter                }
1475251881Speter            }
1476251881Speter
1477251881Speter          if (!found_this_revision)
1478251881Speter            {
1479251881Speter              /* As soon as any PATH is found that is not fully merged for
1480251881Speter                 LOG_ENTRY->REVISION then we can stop. */
1481251881Speter              all_subtrees_have_this_rev = FALSE;
1482251881Speter              break;
1483251881Speter            }
1484251881Speter        }
1485251881Speter
1486251881Speter      svn_pool_destroy(iterpool);
1487251881Speter
1488251881Speter      if (all_subtrees_have_this_rev)
1489251881Speter        {
1490251881Speter          if (fleb->filtering_merged)
1491251881Speter            log_entry->non_inheritable = FALSE;
1492251881Speter          else
1493251881Speter            return SVN_NO_ERROR;
1494251881Speter        }
1495251881Speter    }
1496251881Speter
1497251881Speter  /* Call the wrapped log receiver which this function is filtering for. */
1498251881Speter  return fleb->log_receiver(fleb->log_receiver_baton, log_entry, pool);
1499251881Speter}
1500251881Speter
1501251881Speterstatic svn_error_t *
1502251881Speterlogs_for_mergeinfo_rangelist(const char *source_url,
1503251881Speter                             const apr_array_header_t *merge_source_fspaths,
1504251881Speter                             svn_boolean_t filtering_merged,
1505251881Speter                             const svn_rangelist_t *rangelist,
1506251881Speter                             svn_boolean_t oldest_revs_first,
1507251881Speter                             svn_mergeinfo_catalog_t target_mergeinfo_catalog,
1508251881Speter                             const char *target_fspath,
1509251881Speter                             svn_boolean_t discover_changed_paths,
1510251881Speter                             const apr_array_header_t *revprops,
1511251881Speter                             svn_log_entry_receiver_t log_receiver,
1512251881Speter                             void *log_receiver_baton,
1513251881Speter                             svn_client_ctx_t *ctx,
1514257936Speter                             svn_ra_session_t *ra_session,
1515251881Speter                             apr_pool_t *scratch_pool)
1516251881Speter{
1517251881Speter  svn_merge_range_t *oldest_range, *youngest_range;
1518257936Speter  svn_revnum_t oldest_rev, youngest_rev;
1519251881Speter  struct filter_log_entry_baton_t fleb;
1520251881Speter
1521251881Speter  if (! rangelist->nelts)
1522251881Speter    return SVN_NO_ERROR;
1523251881Speter
1524251881Speter  /* Calculate and construct the bounds of our log request. */
1525251881Speter  youngest_range = APR_ARRAY_IDX(rangelist, rangelist->nelts - 1,
1526251881Speter                                 svn_merge_range_t *);
1527257936Speter  youngest_rev = youngest_range->end;
1528251881Speter  oldest_range = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *);
1529257936Speter  oldest_rev = oldest_range->start;
1530251881Speter
1531251881Speter  if (! target_mergeinfo_catalog)
1532251881Speter    target_mergeinfo_catalog = apr_hash_make(scratch_pool);
1533251881Speter
1534251881Speter  /* FILTER_LOG_ENTRY_BATON_T->TARGET_MERGEINFO_CATALOG's keys are required
1535251881Speter     to be repository-absolute. */
1536251881Speter  SVN_ERR(svn_mergeinfo__add_prefix_to_catalog(&target_mergeinfo_catalog,
1537251881Speter                                               target_mergeinfo_catalog, "/",
1538251881Speter                                               scratch_pool, scratch_pool));
1539251881Speter
1540251881Speter  /* Build the log filtering callback baton. */
1541251881Speter  fleb.filtering_merged = filtering_merged;
1542251881Speter  fleb.merge_source_fspaths = merge_source_fspaths;
1543251881Speter  fleb.target_mergeinfo_catalog = target_mergeinfo_catalog;
1544251881Speter  fleb.depth_first_catalog_index =
1545251881Speter    svn_sort__hash(target_mergeinfo_catalog,
1546251881Speter                   svn_sort_compare_items_as_paths,
1547251881Speter                   scratch_pool);
1548251881Speter  fleb.target_fspath = target_fspath;
1549251881Speter  fleb.rangelist = rangelist;
1550251881Speter  fleb.log_receiver = log_receiver;
1551251881Speter  fleb.log_receiver_baton = log_receiver_baton;
1552251881Speter  fleb.ctx = ctx;
1553251881Speter
1554257936Speter  if (!ra_session)
1555257936Speter    SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, source_url,
1556257936Speter                                                 NULL, NULL, FALSE, FALSE, ctx,
1557257936Speter                                                 scratch_pool, scratch_pool));
1558251881Speter  else
1559257936Speter    SVN_ERR(svn_ra_reparent(ra_session, source_url, scratch_pool));
1560251881Speter
1561257936Speter  {
1562257936Speter    apr_array_header_t *target;
1563257936Speter    target = apr_array_make(scratch_pool, 1, sizeof(const char *));
1564257936Speter    APR_ARRAY_PUSH(target, const char *) = "";
1565257936Speter
1566257936Speter    SVN_ERR(svn_ra_get_log2(ra_session, target,
1567257936Speter                            oldest_revs_first ? oldest_rev : youngest_rev,
1568257936Speter                            oldest_revs_first ? youngest_rev : oldest_rev,
1569257936Speter                            0 /* limit */,
1570257936Speter                            discover_changed_paths,
1571257936Speter                            FALSE /* strict_node_history */,
1572257936Speter                            FALSE /* include_merged_revisions */,
1573257936Speter                            revprops,
1574257936Speter                            filter_log_entry_with_rangelist, &fleb,
1575257936Speter                            scratch_pool));
1576257936Speter  }
1577257936Speter
1578251881Speter  /* Check for cancellation. */
1579251881Speter  if (ctx->cancel_func)
1580251881Speter    SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1581251881Speter
1582251881Speter  return SVN_NO_ERROR;
1583251881Speter}
1584251881Speter
1585251881Speter/* Set *OUT_MERGEINFO to a shallow copy of MERGEINFO with each source path
1586251881Speter   converted to a (URI-encoded) URL based on REPOS_ROOT_URL. *OUT_MERGEINFO
1587251881Speter   is declared as 'apr_hash_t *' because its key do not obey the rules of
1588251881Speter   'svn_mergeinfo_t'.
1589251881Speter
1590251881Speter   Allocate *OUT_MERGEINFO and the new keys in RESULT_POOL.  Use
1591251881Speter   SCRATCH_POOL for any temporary allocations. */
1592251881Speterstatic svn_error_t *
1593251881Spetermergeinfo_relpaths_to_urls(apr_hash_t **out_mergeinfo,
1594251881Speter                           svn_mergeinfo_t mergeinfo,
1595251881Speter                           const char *repos_root_url,
1596251881Speter                           apr_pool_t *result_pool,
1597251881Speter                           apr_pool_t *scratch_pool)
1598251881Speter{
1599251881Speter  *out_mergeinfo = NULL;
1600251881Speter  if (mergeinfo)
1601251881Speter    {
1602251881Speter      apr_hash_index_t *hi;
1603251881Speter      apr_hash_t *full_path_mergeinfo = apr_hash_make(result_pool);
1604251881Speter
1605251881Speter      for (hi = apr_hash_first(scratch_pool, mergeinfo);
1606251881Speter           hi; hi = apr_hash_next(hi))
1607251881Speter        {
1608289180Speter          const char *key = apr_hash_this_key(hi);
1609289180Speter          void *val = apr_hash_this_val(hi);
1610251881Speter
1611251881Speter          svn_hash_sets(full_path_mergeinfo,
1612251881Speter                        svn_path_url_add_component2(repos_root_url, key + 1,
1613251881Speter                                                    result_pool),
1614251881Speter                        val);
1615251881Speter        }
1616251881Speter      *out_mergeinfo = full_path_mergeinfo;
1617251881Speter    }
1618251881Speter
1619251881Speter  return SVN_NO_ERROR;
1620251881Speter}
1621251881Speter
1622251881Speter
1623251881Speter/*** Public APIs ***/
1624251881Speter
1625251881Spetersvn_error_t *
1626251881Spetersvn_client_mergeinfo_get_merged(apr_hash_t **mergeinfo_p,
1627251881Speter                                const char *path_or_url,
1628251881Speter                                const svn_opt_revision_t *peg_revision,
1629251881Speter                                svn_client_ctx_t *ctx,
1630251881Speter                                apr_pool_t *pool)
1631251881Speter{
1632251881Speter  const char *repos_root;
1633251881Speter  svn_mergeinfo_catalog_t mergeinfo_cat;
1634251881Speter  svn_mergeinfo_t mergeinfo;
1635251881Speter
1636251881Speter  SVN_ERR(get_mergeinfo(&mergeinfo_cat, &repos_root, path_or_url,
1637257936Speter                        peg_revision, FALSE, FALSE, ctx, NULL, pool, pool));
1638251881Speter  if (mergeinfo_cat)
1639251881Speter    {
1640251881Speter      const char *repos_relpath;
1641251881Speter
1642251881Speter      if (! svn_path_is_url(path_or_url))
1643251881Speter        {
1644251881Speter          SVN_ERR(svn_dirent_get_absolute(&path_or_url, path_or_url, pool));
1645251881Speter          SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, NULL, NULL,
1646251881Speter                                              ctx->wc_ctx, path_or_url,
1647251881Speter                                              pool, pool));
1648251881Speter        }
1649251881Speter      else
1650251881Speter        {
1651251881Speter          repos_relpath = svn_uri_skip_ancestor(repos_root, path_or_url, pool);
1652251881Speter
1653251881Speter          SVN_ERR_ASSERT(repos_relpath != NULL); /* Or get_mergeinfo failed */
1654251881Speter        }
1655251881Speter
1656251881Speter      mergeinfo = svn_hash_gets(mergeinfo_cat, repos_relpath);
1657251881Speter    }
1658251881Speter  else
1659251881Speter    {
1660251881Speter      mergeinfo = NULL;
1661251881Speter    }
1662251881Speter
1663251881Speter  SVN_ERR(mergeinfo_relpaths_to_urls(mergeinfo_p, mergeinfo,
1664251881Speter                                     repos_root, pool, pool));
1665251881Speter  return SVN_NO_ERROR;
1666251881Speter}
1667251881Speter
1668251881Spetersvn_error_t *
1669253734Spetersvn_client__mergeinfo_log(svn_boolean_t finding_merged,
1670251881Speter                          const char *target_path_or_url,
1671251881Speter                          const svn_opt_revision_t *target_peg_revision,
1672253734Speter                          svn_mergeinfo_catalog_t *target_mergeinfo_catalog,
1673251881Speter                          const char *source_path_or_url,
1674251881Speter                          const svn_opt_revision_t *source_peg_revision,
1675251881Speter                          const svn_opt_revision_t *source_start_revision,
1676251881Speter                          const svn_opt_revision_t *source_end_revision,
1677251881Speter                          svn_log_entry_receiver_t log_receiver,
1678251881Speter                          void *log_receiver_baton,
1679251881Speter                          svn_boolean_t discover_changed_paths,
1680251881Speter                          svn_depth_t depth,
1681251881Speter                          const apr_array_header_t *revprops,
1682251881Speter                          svn_client_ctx_t *ctx,
1683257936Speter                          svn_ra_session_t *ra_session,
1684253734Speter                          apr_pool_t *result_pool,
1685251881Speter                          apr_pool_t *scratch_pool)
1686251881Speter{
1687251881Speter  const char *log_target = NULL;
1688251881Speter  const char *repos_root;
1689251881Speter  const char *target_repos_relpath;
1690251881Speter  svn_mergeinfo_catalog_t target_mergeinfo_cat;
1691253734Speter  svn_ra_session_t *target_session = NULL;
1692253734Speter  svn_client__pathrev_t *pathrev;
1693251881Speter
1694251881Speter  /* A hash of paths, at or under TARGET_PATH_OR_URL, mapped to
1695251881Speter     rangelists.  Not technically mergeinfo, so not using the
1696251881Speter     svn_mergeinfo_t type. */
1697251881Speter  apr_hash_t *inheritable_subtree_merges;
1698251881Speter
1699251881Speter  svn_mergeinfo_t source_history;
1700251881Speter  svn_mergeinfo_t target_history;
1701251881Speter  svn_rangelist_t *master_noninheritable_rangelist;
1702251881Speter  svn_rangelist_t *master_inheritable_rangelist;
1703251881Speter  apr_array_header_t *merge_source_fspaths =
1704251881Speter    apr_array_make(scratch_pool, 1, sizeof(const char *));
1705251881Speter  apr_hash_index_t *hi_catalog;
1706251881Speter  apr_hash_index_t *hi;
1707251881Speter  apr_pool_t *iterpool;
1708251881Speter  svn_boolean_t oldest_revs_first = TRUE;
1709253734Speter  apr_pool_t *subpool;
1710251881Speter
1711251881Speter  /* We currently only support depth = empty | infinity. */
1712251881Speter  if (depth != svn_depth_infinity && depth != svn_depth_empty)
1713251881Speter    return svn_error_create(
1714251881Speter      SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1715251881Speter      _("Only depths 'infinity' and 'empty' are currently supported"));
1716251881Speter
1717251881Speter  /* Validate and sanitize the incoming source operative revision range. */
1718251881Speter  if (!((source_start_revision->kind == svn_opt_revision_unspecified) ||
1719251881Speter        (source_start_revision->kind == svn_opt_revision_number) ||
1720251881Speter        (source_start_revision->kind == svn_opt_revision_date) ||
1721251881Speter        (source_start_revision->kind == svn_opt_revision_head)))
1722251881Speter    return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
1723251881Speter  if (!((source_end_revision->kind == svn_opt_revision_unspecified) ||
1724251881Speter        (source_end_revision->kind == svn_opt_revision_number) ||
1725251881Speter        (source_end_revision->kind == svn_opt_revision_date) ||
1726251881Speter        (source_end_revision->kind == svn_opt_revision_head)))
1727251881Speter    return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
1728251881Speter  if ((source_end_revision->kind != svn_opt_revision_unspecified)
1729251881Speter      && (source_start_revision->kind == svn_opt_revision_unspecified))
1730251881Speter    return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
1731251881Speter  if ((source_end_revision->kind == svn_opt_revision_unspecified)
1732251881Speter      && (source_start_revision->kind != svn_opt_revision_unspecified))
1733251881Speter    return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
1734251881Speter
1735253734Speter  subpool = svn_pool_create(scratch_pool);
1736253734Speter
1737257936Speter  if (ra_session)
1738257936Speter    target_session = ra_session;
1739257936Speter
1740251881Speter  /* We need the union of TARGET_PATH_OR_URL@TARGET_PEG_REVISION's mergeinfo
1741251881Speter     and MERGE_SOURCE_URL's history.  It's not enough to do path
1742251881Speter     matching, because renames in the history of MERGE_SOURCE_URL
1743251881Speter     throw that all in a tizzy.  Of course, if there's no mergeinfo on
1744251881Speter     the target, that vastly simplifies matters (we'll have nothing to
1745251881Speter     do). */
1746251881Speter  /* This get_mergeinfo() call doubles as a mergeinfo capabilities check. */
1747253734Speter  if (target_mergeinfo_catalog)
1748253734Speter    {
1749253734Speter      if (*target_mergeinfo_catalog)
1750253734Speter        {
1751253734Speter          /* The caller provided the mergeinfo catalog for
1752253734Speter             TARGET_PATH_OR_URL, so we don't need to accquire
1753253734Speter             it ourselves.  We do need to get the repos_root
1754253734Speter             though, because get_mergeinfo() won't do it for us. */
1755253734Speter          target_mergeinfo_cat = *target_mergeinfo_catalog;
1756257936Speter
1757257936Speter          if (ra_session && svn_path_is_url(target_path_or_url))
1758257936Speter            {
1759257936Speter              SVN_ERR(svn_ra_reparent(ra_session, target_path_or_url, subpool));
1760257936Speter              SVN_ERR(svn_client__resolve_rev_and_url(&pathrev, ra_session,
1761257936Speter                                                      target_path_or_url,
1762257936Speter                                                      target_peg_revision,
1763257936Speter                                                      target_peg_revision,
1764257936Speter                                                      ctx, subpool));
1765257936Speter              target_session = ra_session;
1766257936Speter            }
1767257936Speter          else
1768257936Speter            {
1769257936Speter              SVN_ERR(svn_client__ra_session_from_path2(&target_session,
1770257936Speter                                                        &pathrev,
1771257936Speter                                                        target_path_or_url,
1772257936Speter                                                        NULL,
1773257936Speter                                                        target_peg_revision,
1774257936Speter                                                        target_peg_revision,
1775257936Speter                                                        ctx, subpool));
1776257936Speter            }
1777253734Speter          SVN_ERR(svn_ra_get_repos_root2(target_session, &repos_root,
1778253734Speter                                         scratch_pool));
1779253734Speter        }
1780253734Speter      else
1781253734Speter        {
1782253734Speter          /* The caller didn't provide the mergeinfo catalog for
1783253734Speter             TARGET_PATH_OR_URL, but wants us to pass a copy back
1784253734Speter             when we get it, so use RESULT_POOL. */
1785253734Speter          SVN_ERR(get_mergeinfo(target_mergeinfo_catalog, &repos_root,
1786253734Speter                                target_path_or_url, target_peg_revision,
1787253734Speter                                depth == svn_depth_infinity, TRUE,
1788257936Speter                                ctx, ra_session, result_pool, scratch_pool));
1789253734Speter          target_mergeinfo_cat = *target_mergeinfo_catalog;
1790253734Speter        }
1791253734Speter    }
1792253734Speter  else
1793253734Speter    {
1794253734Speter      /* The caller didn't provide the mergeinfo catalog for
1795253734Speter         TARGET_PATH_OR_URL, nor does it want a copy, so we can use
1796253734Speter         nothing but SCRATCH_POOL. */
1797253734Speter      SVN_ERR(get_mergeinfo(&target_mergeinfo_cat, &repos_root,
1798253734Speter                            target_path_or_url, target_peg_revision,
1799253734Speter                            depth == svn_depth_infinity, TRUE,
1800257936Speter                            ctx, ra_session, scratch_pool, scratch_pool));
1801253734Speter    }
1802251881Speter
1803251881Speter  if (!svn_path_is_url(target_path_or_url))
1804251881Speter    {
1805251881Speter      SVN_ERR(svn_dirent_get_absolute(&target_path_or_url,
1806251881Speter                                      target_path_or_url, scratch_pool));
1807251881Speter      SVN_ERR(svn_wc__node_get_repos_info(NULL, &target_repos_relpath,
1808251881Speter                                          NULL, NULL,
1809251881Speter                                          ctx->wc_ctx, target_path_or_url,
1810251881Speter                                          scratch_pool, scratch_pool));
1811251881Speter    }
1812251881Speter  else
1813251881Speter    {
1814251881Speter      target_repos_relpath = svn_uri_skip_ancestor(repos_root,
1815251881Speter                                                   target_path_or_url,
1816251881Speter                                                   scratch_pool);
1817251881Speter
1818251881Speter      /* TARGET_REPOS_REL should be non-NULL, else get_mergeinfo
1819251881Speter         should have failed.  */
1820251881Speter      SVN_ERR_ASSERT(target_repos_relpath != NULL);
1821251881Speter    }
1822251881Speter
1823251881Speter  if (!target_mergeinfo_cat)
1824251881Speter    {
1825251881Speter      /* If we are looking for what has been merged and there is no
1826251881Speter         mergeinfo then we already know the answer.  If we are looking
1827251881Speter         for eligible revisions then create a catalog with empty mergeinfo
1828251881Speter         on the target.  This is semantically equivalent to no mergeinfo
1829251881Speter         and gives us something to combine with MERGE_SOURCE_URL's
1830251881Speter         history. */
1831251881Speter      if (finding_merged)
1832251881Speter        {
1833253734Speter          svn_pool_destroy(subpool);
1834251881Speter          return SVN_NO_ERROR;
1835251881Speter        }
1836251881Speter      else
1837251881Speter        {
1838251881Speter          target_mergeinfo_cat = apr_hash_make(scratch_pool);
1839251881Speter          svn_hash_sets(target_mergeinfo_cat, target_repos_relpath,
1840251881Speter                        apr_hash_make(scratch_pool));
1841251881Speter        }
1842251881Speter    }
1843251881Speter
1844251881Speter  /* Fetch the location history as mergeinfo, for the source branch
1845251881Speter   * (between the given start and end revisions), and, if we're finding
1846251881Speter   * merged revisions, then also for the entire target branch.
1847251881Speter   *
1848251881Speter   * ### TODO: As the source and target must be in the same repository, we
1849251881Speter   * should share a single session, tracking the two URLs separately. */
1850251881Speter  {
1851253734Speter    svn_ra_session_t *source_session;
1852251881Speter    svn_revnum_t start_rev, end_rev, youngest_rev = SVN_INVALID_REVNUM;
1853251881Speter
1854251881Speter    if (! finding_merged)
1855251881Speter      {
1856253734Speter        if (!target_session)
1857253734Speter          SVN_ERR(svn_client__ra_session_from_path2(&target_session, &pathrev,
1858253734Speter                                                    target_path_or_url, NULL,
1859253734Speter                                                    target_peg_revision,
1860253734Speter                                                    target_peg_revision,
1861253734Speter                                                    ctx, subpool));
1862251881Speter        SVN_ERR(svn_client__get_history_as_mergeinfo(&target_history, NULL,
1863251881Speter                                                     pathrev,
1864251881Speter                                                     SVN_INVALID_REVNUM,
1865251881Speter                                                     SVN_INVALID_REVNUM,
1866251881Speter                                                     target_session, ctx,
1867251881Speter                                                     scratch_pool));
1868251881Speter      }
1869251881Speter
1870257936Speter    if (target_session
1871257936Speter        && svn_path_is_url(source_path_or_url)
1872257936Speter        && repos_root
1873257936Speter        && svn_uri_skip_ancestor(repos_root, source_path_or_url, subpool))
1874257936Speter      {
1875257936Speter        /* We can re-use the existing session */
1876257936Speter        source_session = target_session;
1877257936Speter        SVN_ERR(svn_ra_reparent(source_session, source_path_or_url, subpool));
1878257936Speter        SVN_ERR(svn_client__resolve_rev_and_url(&pathrev, source_session,
1879257936Speter                                                source_path_or_url,
1880257936Speter                                                source_peg_revision,
1881257936Speter                                                source_peg_revision,
1882257936Speter                                                ctx, subpool));
1883257936Speter      }
1884257936Speter    else
1885257936Speter      {
1886257936Speter        SVN_ERR(svn_client__ra_session_from_path2(&source_session, &pathrev,
1887257936Speter                                                  source_path_or_url, NULL,
1888257936Speter                                                  source_peg_revision,
1889257936Speter                                                  source_peg_revision,
1890257936Speter                                                  ctx, subpool));
1891257936Speter      }
1892251881Speter    SVN_ERR(svn_client__get_revision_number(&start_rev, &youngest_rev,
1893251881Speter                                            ctx->wc_ctx, source_path_or_url,
1894251881Speter                                            source_session,
1895251881Speter                                            source_start_revision,
1896253734Speter                                            subpool));
1897251881Speter    SVN_ERR(svn_client__get_revision_number(&end_rev, &youngest_rev,
1898251881Speter                                            ctx->wc_ctx, source_path_or_url,
1899251881Speter                                            source_session,
1900251881Speter                                            source_end_revision,
1901253734Speter                                            subpool));
1902251881Speter    SVN_ERR(svn_client__get_history_as_mergeinfo(&source_history, NULL,
1903251881Speter                                                 pathrev,
1904251881Speter                                                 MAX(end_rev, start_rev),
1905251881Speter                                                 MIN(end_rev, start_rev),
1906251881Speter                                                 source_session, ctx,
1907251881Speter                                                 scratch_pool));
1908251881Speter    if (start_rev > end_rev)
1909251881Speter      oldest_revs_first = FALSE;
1910251881Speter  }
1911251881Speter
1912251881Speter  /* Separate the explicit or inherited mergeinfo on TARGET_PATH_OR_URL,
1913251881Speter     and possibly its explicit subtree mergeinfo, into their
1914251881Speter     inheritable and non-inheritable parts. */
1915251881Speter  master_noninheritable_rangelist = apr_array_make(scratch_pool, 64,
1916251881Speter                                                   sizeof(svn_merge_range_t *));
1917251881Speter  master_inheritable_rangelist = apr_array_make(scratch_pool, 64,
1918251881Speter                                                sizeof(svn_merge_range_t *));
1919251881Speter  inheritable_subtree_merges = apr_hash_make(scratch_pool);
1920251881Speter
1921251881Speter  iterpool = svn_pool_create(scratch_pool);
1922251881Speter
1923251881Speter  for (hi_catalog = apr_hash_first(scratch_pool, target_mergeinfo_cat);
1924251881Speter       hi_catalog;
1925251881Speter       hi_catalog = apr_hash_next(hi_catalog))
1926251881Speter    {
1927289180Speter      svn_mergeinfo_t subtree_mergeinfo = apr_hash_this_val(hi_catalog);
1928251881Speter      svn_mergeinfo_t subtree_history;
1929251881Speter      svn_mergeinfo_t subtree_source_history;
1930251881Speter      svn_mergeinfo_t subtree_inheritable_mergeinfo;
1931251881Speter      svn_mergeinfo_t subtree_noninheritable_mergeinfo;
1932251881Speter      svn_mergeinfo_t merged_noninheritable;
1933251881Speter      svn_mergeinfo_t merged;
1934289180Speter      const char *subtree_path = apr_hash_this_key(hi_catalog);
1935251881Speter      svn_boolean_t is_subtree = strcmp(subtree_path,
1936251881Speter                                        target_repos_relpath) != 0;
1937251881Speter      svn_pool_clear(iterpool);
1938251881Speter
1939251881Speter      if (is_subtree)
1940251881Speter        {
1941251881Speter          /* If SUBTREE_PATH is a proper subtree of TARGET_PATH_OR_URL
1942251881Speter             then make a copy of SOURCE_HISTORY that is path adjusted
1943251881Speter             for the subtree.  */
1944251881Speter          const char *subtree_rel_path =
1945251881Speter            subtree_path + strlen(target_repos_relpath) + 1;
1946251881Speter
1947251881Speter          SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
1948251881Speter            &subtree_source_history, source_history,
1949362181Sdim            subtree_rel_path, scratch_pool, iterpool));
1950251881Speter
1951251881Speter          if (!finding_merged)
1952251881Speter            SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
1953251881Speter                    &subtree_history, target_history,
1954362181Sdim                    subtree_rel_path, scratch_pool, iterpool));
1955251881Speter        }
1956251881Speter      else
1957251881Speter        {
1958251881Speter          subtree_source_history = source_history;
1959251881Speter          if (!finding_merged)
1960251881Speter            subtree_history = target_history;
1961251881Speter        }
1962251881Speter
1963251881Speter      if (!finding_merged)
1964251881Speter        {
1965251881Speter          svn_mergeinfo_t merged_via_history;
1966251881Speter          SVN_ERR(svn_mergeinfo_intersect2(&merged_via_history,
1967251881Speter                                           subtree_history,
1968251881Speter                                           subtree_source_history, TRUE,
1969251881Speter                                           scratch_pool, iterpool));
1970251881Speter          SVN_ERR(svn_mergeinfo_merge2(subtree_mergeinfo,
1971251881Speter                                       merged_via_history,
1972362181Sdim                                       scratch_pool, iterpool));
1973251881Speter        }
1974251881Speter
1975251881Speter      SVN_ERR(svn_mergeinfo_inheritable2(&subtree_inheritable_mergeinfo,
1976251881Speter                                         subtree_mergeinfo, NULL,
1977251881Speter                                         SVN_INVALID_REVNUM,
1978251881Speter                                         SVN_INVALID_REVNUM,
1979251881Speter                                         TRUE, scratch_pool, iterpool));
1980251881Speter      SVN_ERR(svn_mergeinfo_inheritable2(&subtree_noninheritable_mergeinfo,
1981251881Speter                                         subtree_mergeinfo, NULL,
1982251881Speter                                         SVN_INVALID_REVNUM,
1983251881Speter                                         SVN_INVALID_REVNUM,
1984251881Speter                                         FALSE, scratch_pool, iterpool));
1985251881Speter
1986251881Speter      /* Find the intersection of the non-inheritable part of
1987251881Speter         SUBTREE_MERGEINFO and SOURCE_HISTORY.  svn_mergeinfo_intersect2()
1988251881Speter         won't consider non-inheritable and inheritable ranges
1989251881Speter         intersecting unless we ignore inheritance, but in doing so the
1990251881Speter         resulting intersections have all inheritable ranges.  To get
1991251881Speter         around this we set the inheritance on the result to all
1992251881Speter         non-inheritable. */
1993251881Speter      SVN_ERR(svn_mergeinfo_intersect2(&merged_noninheritable,
1994251881Speter                                       subtree_noninheritable_mergeinfo,
1995251881Speter                                       subtree_source_history, FALSE,
1996251881Speter                                       scratch_pool, iterpool));
1997251881Speter      svn_mergeinfo__set_inheritance(merged_noninheritable, FALSE,
1998362181Sdim                                     iterpool);
1999251881Speter
2000251881Speter      /* Keep track of all ranges partially merged to any and all
2001251881Speter         subtrees. */
2002251881Speter      SVN_ERR(svn_rangelist__merge_many(master_noninheritable_rangelist,
2003251881Speter                                        merged_noninheritable,
2004251881Speter                                        scratch_pool, iterpool));
2005251881Speter
2006251881Speter      /* Find the intersection of the inheritable part of TGT_MERGEINFO
2007251881Speter         and SOURCE_HISTORY. */
2008251881Speter      SVN_ERR(svn_mergeinfo_intersect2(&merged,
2009251881Speter                                       subtree_inheritable_mergeinfo,
2010251881Speter                                       subtree_source_history, FALSE,
2011251881Speter                                       scratch_pool, iterpool));
2012251881Speter
2013251881Speter      /* Keep track of all ranges fully merged to any and all
2014251881Speter         subtrees. */
2015251881Speter      if (apr_hash_count(merged))
2016251881Speter        {
2017251881Speter          /* The inheritable rangelist merged from SUBTREE_SOURCE_HISTORY
2018251881Speter             to SUBTREE_PATH. */
2019251881Speter          svn_rangelist_t *subtree_merged_rangelist =
2020251881Speter            apr_array_make(scratch_pool, 1, sizeof(svn_merge_range_t *));
2021251881Speter
2022251881Speter          SVN_ERR(svn_rangelist__merge_many(master_inheritable_rangelist,
2023251881Speter                                            merged, scratch_pool, iterpool));
2024251881Speter          SVN_ERR(svn_rangelist__merge_many(subtree_merged_rangelist,
2025251881Speter                                            merged, scratch_pool, iterpool));
2026251881Speter
2027251881Speter          svn_hash_sets(inheritable_subtree_merges, subtree_path,
2028251881Speter                        subtree_merged_rangelist);
2029251881Speter        }
2030251881Speter      else
2031251881Speter        {
2032251881Speter          /* Map SUBTREE_PATH to an empty rangelist if there was nothing
2033251881Speter             fully merged. e.g. Only empty or non-inheritable mergeinfo
2034251881Speter             on the subtree or mergeinfo unrelated to the source. */
2035251881Speter          svn_hash_sets(inheritable_subtree_merges, subtree_path,
2036251881Speter                        apr_array_make(scratch_pool, 0,
2037251881Speter                                       sizeof(svn_merge_range_t *)));
2038251881Speter        }
2039251881Speter    }
2040251881Speter
2041251881Speter  /* Make sure every range in MASTER_INHERITABLE_RANGELIST is fully merged to
2042251881Speter     each subtree (including the target itself).  Any revisions which don't
2043251881Speter     exist in *every* subtree are *potentially* only partially merged to the
2044251881Speter     tree rooted at TARGET_PATH_OR_URL, so move those revisions to
2045251881Speter     MASTER_NONINHERITABLE_RANGELIST.  It may turn out that that a revision
2046251881Speter     was merged to the only subtree it affects, but we need to examine the
2047251881Speter     logs to make this determination (which will be done by
2048251881Speter     logs_for_mergeinfo_rangelist). */
2049251881Speter  if (master_inheritable_rangelist->nelts)
2050251881Speter    {
2051251881Speter      for (hi = apr_hash_first(scratch_pool, inheritable_subtree_merges);
2052251881Speter           hi;
2053251881Speter           hi = apr_hash_next(hi))
2054251881Speter        {
2055251881Speter          svn_rangelist_t *deleted_rangelist;
2056251881Speter          svn_rangelist_t *added_rangelist;
2057289180Speter          svn_rangelist_t *subtree_merged_rangelist = apr_hash_this_val(hi);
2058251881Speter
2059251881Speter          svn_pool_clear(iterpool);
2060251881Speter
2061251881Speter          SVN_ERR(svn_rangelist_diff(&deleted_rangelist, &added_rangelist,
2062251881Speter                                     master_inheritable_rangelist,
2063251881Speter                                     subtree_merged_rangelist, TRUE,
2064251881Speter                                     iterpool));
2065251881Speter
2066251881Speter          if (deleted_rangelist->nelts)
2067251881Speter            {
2068251881Speter              svn_rangelist__set_inheritance(deleted_rangelist, FALSE);
2069251881Speter              SVN_ERR(svn_rangelist_merge2(master_noninheritable_rangelist,
2070251881Speter                                           deleted_rangelist,
2071251881Speter                                           scratch_pool, iterpool));
2072251881Speter              SVN_ERR(svn_rangelist_remove(&master_inheritable_rangelist,
2073251881Speter                                           deleted_rangelist,
2074251881Speter                                           master_inheritable_rangelist,
2075251881Speter                                           FALSE,
2076251881Speter                                           scratch_pool));
2077251881Speter            }
2078251881Speter        }
2079251881Speter    }
2080251881Speter
2081251881Speter  if (finding_merged)
2082251881Speter    {
2083251881Speter      /* Roll all the merged revisions into one rangelist. */
2084251881Speter      SVN_ERR(svn_rangelist_merge2(master_inheritable_rangelist,
2085251881Speter                                   master_noninheritable_rangelist,
2086251881Speter                                   scratch_pool, scratch_pool));
2087251881Speter
2088251881Speter    }
2089251881Speter  else
2090251881Speter    {
2091251881Speter      /* Create the starting rangelist for what might be eligible. */
2092251881Speter      svn_rangelist_t *source_master_rangelist =
2093251881Speter        apr_array_make(scratch_pool, 1, sizeof(svn_merge_range_t *));
2094251881Speter
2095251881Speter      SVN_ERR(svn_rangelist__merge_many(source_master_rangelist,
2096251881Speter                                        source_history,
2097251881Speter                                        scratch_pool, scratch_pool));
2098251881Speter
2099251881Speter      /* From what might be eligible subtract what we know is
2100251881Speter         partially merged and then merge that back. */
2101251881Speter      SVN_ERR(svn_rangelist_remove(&source_master_rangelist,
2102251881Speter                                   master_noninheritable_rangelist,
2103251881Speter                                   source_master_rangelist,
2104251881Speter                                   FALSE, scratch_pool));
2105251881Speter      SVN_ERR(svn_rangelist_merge2(source_master_rangelist,
2106251881Speter                                   master_noninheritable_rangelist,
2107251881Speter                                   scratch_pool, scratch_pool));
2108251881Speter      SVN_ERR(svn_rangelist_remove(&master_inheritable_rangelist,
2109251881Speter                                   master_inheritable_rangelist,
2110251881Speter                                   source_master_rangelist,
2111251881Speter                                   TRUE, scratch_pool));
2112251881Speter    }
2113251881Speter
2114251881Speter  /* Nothing merged?  Not even when considering shared history if
2115251881Speter     looking for eligible revisions (i.e. !FINDING_MERGED)?  Then there
2116251881Speter     is nothing more to do. */
2117251881Speter  if (! master_inheritable_rangelist->nelts)
2118251881Speter    {
2119251881Speter      svn_pool_destroy(iterpool);
2120251881Speter      return SVN_NO_ERROR;
2121251881Speter    }
2122251881Speter  else
2123251881Speter    {
2124251881Speter      /* Determine the correct (youngest) target for 'svn log'. */
2125251881Speter      svn_merge_range_t *youngest_range
2126251881Speter        = APR_ARRAY_IDX(master_inheritable_rangelist,
2127251881Speter                        master_inheritable_rangelist->nelts - 1,
2128251881Speter                        svn_merge_range_t *);
2129251881Speter      svn_rangelist_t *youngest_rangelist =
2130251881Speter        svn_rangelist__initialize(youngest_range->end - 1,
2131251881Speter                                  youngest_range->end,
2132251881Speter                                  youngest_range->inheritable,
2133289180Speter                                  scratch_pool);
2134251881Speter
2135251881Speter      for (hi = apr_hash_first(scratch_pool, source_history);
2136251881Speter           hi;
2137251881Speter           hi = apr_hash_next(hi))
2138251881Speter        {
2139289180Speter          const char *key = apr_hash_this_key(hi);
2140289180Speter          svn_rangelist_t *subtree_merged_rangelist = apr_hash_this_val(hi);
2141251881Speter          svn_rangelist_t *intersecting_rangelist;
2142251881Speter
2143251881Speter          svn_pool_clear(iterpool);
2144251881Speter          SVN_ERR(svn_rangelist_intersect(&intersecting_rangelist,
2145251881Speter                                          youngest_rangelist,
2146251881Speter                                          subtree_merged_rangelist,
2147251881Speter                                          FALSE, iterpool));
2148251881Speter
2149251881Speter          APR_ARRAY_PUSH(merge_source_fspaths, const char *) = key;
2150251881Speter
2151251881Speter          if (intersecting_rangelist->nelts)
2152251881Speter            log_target = key;
2153251881Speter        }
2154251881Speter    }
2155251881Speter
2156251881Speter  svn_pool_destroy(iterpool);
2157251881Speter
2158251881Speter  /* Step 4: Finally, we run 'svn log' to drive our log receiver, but
2159251881Speter     using a receiver filter to only allow revisions to pass through
2160251881Speter     that are in our rangelist. */
2161251881Speter  log_target = svn_path_url_add_component2(repos_root, log_target + 1,
2162251881Speter                                           scratch_pool);
2163251881Speter
2164257936Speter  {
2165257936Speter    svn_error_t *err;
2166257936Speter
2167257936Speter    err = logs_for_mergeinfo_rangelist(log_target, merge_source_fspaths,
2168251881Speter                                       finding_merged,
2169251881Speter                                       master_inheritable_rangelist,
2170251881Speter                                       oldest_revs_first,
2171251881Speter                                       target_mergeinfo_cat,
2172251881Speter                                       svn_fspath__join("/",
2173251881Speter                                                        target_repos_relpath,
2174251881Speter                                                        scratch_pool),
2175251881Speter                                       discover_changed_paths,
2176251881Speter                                       revprops,
2177251881Speter                                       log_receiver, log_receiver_baton,
2178257936Speter                                       ctx, target_session, scratch_pool);
2179257936Speter
2180257936Speter    /* Close the source and target sessions. */
2181257936Speter    svn_pool_destroy(subpool); /* For SVN_ERR_CEASE_INVOCATION */
2182257936Speter
2183257936Speter    return svn_error_trace(err);
2184257936Speter  }
2185251881Speter}
2186251881Speter
2187251881Spetersvn_error_t *
2188253734Spetersvn_client_mergeinfo_log2(svn_boolean_t finding_merged,
2189253734Speter                          const char *target_path_or_url,
2190253734Speter                          const svn_opt_revision_t *target_peg_revision,
2191253734Speter                          const char *source_path_or_url,
2192253734Speter                          const svn_opt_revision_t *source_peg_revision,
2193253734Speter                          const svn_opt_revision_t *source_start_revision,
2194253734Speter                          const svn_opt_revision_t *source_end_revision,
2195253734Speter                          svn_log_entry_receiver_t log_receiver,
2196253734Speter                          void *log_receiver_baton,
2197253734Speter                          svn_boolean_t discover_changed_paths,
2198253734Speter                          svn_depth_t depth,
2199253734Speter                          const apr_array_header_t *revprops,
2200253734Speter                          svn_client_ctx_t *ctx,
2201253734Speter                          apr_pool_t *scratch_pool)
2202253734Speter{
2203257936Speter  return svn_error_trace(
2204257936Speter         svn_client__mergeinfo_log(finding_merged, target_path_or_url,
2205253734Speter                                   target_peg_revision, NULL,
2206253734Speter                                   source_path_or_url, source_peg_revision,
2207253734Speter                                   source_start_revision, source_end_revision,
2208253734Speter                                   log_receiver, log_receiver_baton,
2209253734Speter                                   discover_changed_paths, depth, revprops,
2210257936Speter                                   ctx, NULL,
2211257936Speter                                   scratch_pool, scratch_pool));
2212253734Speter}
2213253734Speter
2214253734Spetersvn_error_t *
2215251881Spetersvn_client_suggest_merge_sources(apr_array_header_t **suggestions,
2216251881Speter                                 const char *path_or_url,
2217251881Speter                                 const svn_opt_revision_t *peg_revision,
2218251881Speter                                 svn_client_ctx_t *ctx,
2219251881Speter                                 apr_pool_t *pool)
2220251881Speter{
2221251881Speter  const char *repos_root;
2222251881Speter  const char *copyfrom_path;
2223251881Speter  apr_array_header_t *list;
2224251881Speter  svn_revnum_t copyfrom_rev;
2225251881Speter  svn_mergeinfo_catalog_t mergeinfo_cat;
2226251881Speter  svn_mergeinfo_t mergeinfo;
2227251881Speter  apr_hash_index_t *hi;
2228289180Speter  apr_pool_t *session_pool = svn_pool_create(pool);
2229289180Speter  svn_ra_session_t *ra_session;
2230251881Speter
2231251881Speter  list = apr_array_make(pool, 1, sizeof(const char *));
2232251881Speter
2233251881Speter  /* In our ideal algorithm, the list of recommendations should be
2234251881Speter     ordered by:
2235251881Speter
2236251881Speter        1. The most recent existing merge source.
2237251881Speter        2. The copyfrom source (which will also be listed as a merge
2238251881Speter           source if the copy was made with a 1.5+ client and server).
2239251881Speter        3. All other merge sources, most recent to least recent.
2240251881Speter
2241251881Speter     However, determining the order of application of merge sources
2242251881Speter     requires a new RA API.  Until such an API is available, our
2243251881Speter     algorithm will be:
2244251881Speter
2245251881Speter        1. The copyfrom source.
2246251881Speter        2. All remaining merge sources (unordered).
2247251881Speter  */
2248289180Speter  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, NULL, path_or_url,
2249289180Speter                                            NULL, peg_revision, peg_revision,
2250289180Speter                                            ctx, session_pool));
2251251881Speter
2252251881Speter  SVN_ERR(get_mergeinfo(&mergeinfo_cat, &repos_root, path_or_url,
2253289180Speter                        peg_revision, FALSE, FALSE,
2254289180Speter                        ctx, ra_session, session_pool, session_pool));
2255251881Speter
2256251881Speter  if (mergeinfo_cat && apr_hash_count(mergeinfo_cat))
2257251881Speter    {
2258251881Speter      /* We asked only for the PATH_OR_URL's mergeinfo, not any of its
2259251881Speter         descendants.  So if there is anything in the catalog it is the
2260251881Speter         mergeinfo for PATH_OR_URL. */
2261289180Speter      mergeinfo = apr_hash_this_val(apr_hash_first(session_pool,
2262289180Speter                                                   mergeinfo_cat));
2263251881Speter    }
2264251881Speter  else
2265251881Speter    {
2266251881Speter      mergeinfo = NULL;
2267251881Speter    }
2268251881Speter
2269289180Speter  /* ### Should we only add the last source or all copy sources back to
2270289180Speter         the origin? */
2271251881Speter  SVN_ERR(svn_client__get_copy_source(&copyfrom_path, &copyfrom_rev,
2272289180Speter                                      path_or_url, peg_revision, ra_session,
2273289180Speter                                      ctx, session_pool, session_pool));
2274251881Speter  if (copyfrom_path)
2275251881Speter    {
2276251881Speter      APR_ARRAY_PUSH(list, const char *) =
2277251881Speter        svn_path_url_add_component2(repos_root, copyfrom_path, pool);
2278251881Speter    }
2279251881Speter
2280251881Speter  if (mergeinfo)
2281251881Speter    {
2282289180Speter      for (hi = apr_hash_first(session_pool, mergeinfo);
2283289180Speter           hi;
2284289180Speter           hi = apr_hash_next(hi))
2285251881Speter        {
2286289180Speter          const char *rel_path = apr_hash_this_key(hi);
2287251881Speter
2288251881Speter          if (copyfrom_path == NULL || strcmp(rel_path, copyfrom_path) != 0)
2289251881Speter            APR_ARRAY_PUSH(list, const char *) = \
2290251881Speter              svn_path_url_add_component2(repos_root, rel_path + 1, pool);
2291251881Speter        }
2292251881Speter    }
2293251881Speter
2294289180Speter  svn_pool_destroy(session_pool);
2295289180Speter
2296251881Speter  *suggestions = list;
2297251881Speter  return SVN_NO_ERROR;
2298251881Speter}
2299251881Speter
2300251881Spetersvn_error_t *
2301251881Spetersvn_client__mergeinfo_status(svn_boolean_t *mergeinfo_changes,
2302251881Speter                             svn_wc_context_t *wc_ctx,
2303251881Speter                             const char *local_abspath,
2304251881Speter                             apr_pool_t *scratch_pool)
2305251881Speter{
2306251881Speter  apr_array_header_t *propchanges;
2307251881Speter  int i;
2308251881Speter
2309251881Speter  *mergeinfo_changes = FALSE;
2310251881Speter
2311251881Speter  SVN_ERR(svn_wc_get_prop_diffs2(&propchanges, NULL, wc_ctx,
2312251881Speter                                 local_abspath, scratch_pool, scratch_pool));
2313251881Speter
2314251881Speter  for (i = 0; i < propchanges->nelts; i++)
2315251881Speter    {
2316251881Speter      svn_prop_t prop = APR_ARRAY_IDX(propchanges, i, svn_prop_t);
2317251881Speter      if (strcmp(prop.name, SVN_PROP_MERGEINFO) == 0)
2318251881Speter        {
2319251881Speter          *mergeinfo_changes = TRUE;
2320251881Speter          break;
2321251881Speter        }
2322251881Speter    }
2323251881Speter
2324251881Speter  return SVN_NO_ERROR;
2325251881Speter}
2326