log.c revision 251886
1130803Smarcel/* log.c --- retrieving log messages
2130803Smarcel *
3130803Smarcel * ====================================================================
4130803Smarcel *    Licensed to the Apache Software Foundation (ASF) under one
5130803Smarcel *    or more contributor license agreements.  See the NOTICE file
6130803Smarcel *    distributed with this work for additional information
7130803Smarcel *    regarding copyright ownership.  The ASF licenses this file
8130803Smarcel *    to you under the Apache License, Version 2.0 (the
9130803Smarcel *    "License"); you may not use this file except in compliance
10130803Smarcel *    with the License.  You may obtain a copy of the License at
11130803Smarcel *
12130803Smarcel *      http://www.apache.org/licenses/LICENSE-2.0
13130803Smarcel *
14130803Smarcel *    Unless required by applicable law or agreed to in writing,
15130803Smarcel *    software distributed under the License is distributed on an
16130803Smarcel *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17130803Smarcel *    KIND, either express or implied.  See the License for the
18130803Smarcel *    specific language governing permissions and limitations
19130803Smarcel *    under the License.
20130803Smarcel * ====================================================================
21130803Smarcel */
22130803Smarcel
23130803Smarcel
24130803Smarcel#include <stdlib.h>
25130803Smarcel#define APR_WANT_STRFUNC
26130803Smarcel#include <apr_want.h>
27130803Smarcel
28130803Smarcel#include "svn_compat.h"
29130803Smarcel#include "svn_private_config.h"
30130803Smarcel#include "svn_hash.h"
31130803Smarcel#include "svn_pools.h"
32130803Smarcel#include "svn_error.h"
33130803Smarcel#include "svn_path.h"
34130803Smarcel#include "svn_fs.h"
35130803Smarcel#include "svn_repos.h"
36130803Smarcel#include "svn_string.h"
37130803Smarcel#include "svn_sorts.h"
38130803Smarcel#include "svn_props.h"
39130803Smarcel#include "svn_mergeinfo.h"
40130803Smarcel#include "repos.h"
41130803Smarcel#include "private/svn_fspath.h"
42130803Smarcel#include "private/svn_mergeinfo_private.h"
43130803Smarcel#include "private/svn_subr_private.h"
44130803Smarcel
45130803Smarcel
46130803Smarcel
47130803Smarcelsvn_error_t *
48130803Smarcelsvn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
49130803Smarcel                                svn_repos_t *repos,
50130803Smarcel                                svn_revnum_t revision,
51130803Smarcel                                svn_repos_authz_func_t authz_read_func,
52130803Smarcel                                void *authz_read_baton,
53130803Smarcel                                apr_pool_t *pool)
54130803Smarcel{
55130803Smarcel  svn_fs_t *fs = svn_repos_fs(repos);
56130803Smarcel  svn_fs_root_t *rev_root;
57130803Smarcel  apr_hash_t *changes;
58130803Smarcel  apr_hash_index_t *hi;
59130803Smarcel  svn_boolean_t found_readable = FALSE;
60130803Smarcel  svn_boolean_t found_unreadable = FALSE;
61130803Smarcel  apr_pool_t *subpool;
62130803Smarcel
63130803Smarcel  /* By default, we'll grant full read access to REVISION. */
64130803Smarcel  *access_level = svn_repos_revision_access_full;
65130803Smarcel
66130803Smarcel  /* No auth-checking function?  We're done. */
67130803Smarcel  if (! authz_read_func)
68130803Smarcel    return SVN_NO_ERROR;
69130803Smarcel
70130803Smarcel  /* Fetch the changes associated with REVISION. */
71130803Smarcel  SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool));
72130803Smarcel  SVN_ERR(svn_fs_paths_changed2(&changes, rev_root, pool));
73130803Smarcel
74130803Smarcel  /* No changed paths?  We're done. */
75130803Smarcel  if (apr_hash_count(changes) == 0)
76130803Smarcel    return SVN_NO_ERROR;
77130803Smarcel
78130803Smarcel  /* Otherwise, we have to check the readability of each changed
79130803Smarcel     path, or at least enough to answer the question asked. */
80130803Smarcel  subpool = svn_pool_create(pool);
81130803Smarcel  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
82130803Smarcel    {
83130803Smarcel      const void *key;
84130803Smarcel      void *val;
85130803Smarcel      svn_fs_path_change2_t *change;
86130803Smarcel      svn_boolean_t readable;
87130803Smarcel
88130803Smarcel      svn_pool_clear(subpool);
89130803Smarcel      apr_hash_this(hi, &key, NULL, &val);
90130803Smarcel      change = val;
91130803Smarcel
92130803Smarcel      SVN_ERR(authz_read_func(&readable, rev_root, key,
93130803Smarcel                              authz_read_baton, subpool));
94130803Smarcel      if (! readable)
95130803Smarcel        found_unreadable = TRUE;
96130803Smarcel      else
97130803Smarcel        found_readable = TRUE;
98130803Smarcel
99130803Smarcel      /* If we have at least one of each (readable/unreadable), we
100130803Smarcel         have our answer. */
101130803Smarcel      if (found_readable && found_unreadable)
102130803Smarcel        goto decision;
103130803Smarcel
104130803Smarcel      switch (change->change_kind)
105130803Smarcel        {
106130803Smarcel        case svn_fs_path_change_add:
107130803Smarcel        case svn_fs_path_change_replace:
108130803Smarcel          {
109130803Smarcel            const char *copyfrom_path;
110130803Smarcel            svn_revnum_t copyfrom_rev;
111130803Smarcel
112130803Smarcel            SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
113130803Smarcel                                       rev_root, key, subpool));
114130803Smarcel            if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
115130803Smarcel              {
116130803Smarcel                svn_fs_root_t *copyfrom_root;
117130803Smarcel                SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
118130803Smarcel                                             copyfrom_rev, subpool));
119130803Smarcel                SVN_ERR(authz_read_func(&readable,
120130803Smarcel                                        copyfrom_root, copyfrom_path,
121130803Smarcel                                        authz_read_baton, subpool));
122130803Smarcel                if (! readable)
123130803Smarcel                  found_unreadable = TRUE;
124130803Smarcel
125130803Smarcel                /* If we have at least one of each (readable/unreadable), we
126130803Smarcel                   have our answer. */
127130803Smarcel                if (found_readable && found_unreadable)
128130803Smarcel                  goto decision;
129130803Smarcel              }
130130803Smarcel          }
131130803Smarcel          break;
132130803Smarcel
133130803Smarcel        case svn_fs_path_change_delete:
134130803Smarcel        case svn_fs_path_change_modify:
135130803Smarcel        default:
136130803Smarcel          break;
137130803Smarcel        }
138130803Smarcel    }
139130803Smarcel
140130803Smarcel decision:
141130803Smarcel  svn_pool_destroy(subpool);
142130803Smarcel
143130803Smarcel  /* Either every changed path was unreadable... */
144130803Smarcel  if (! found_readable)
145130803Smarcel    *access_level = svn_repos_revision_access_none;
146130803Smarcel
147130803Smarcel  /* ... or some changed path was unreadable... */
148130803Smarcel  else if (found_unreadable)
149130803Smarcel    *access_level = svn_repos_revision_access_partial;
150130803Smarcel
151130803Smarcel  /* ... or every changed path was readable (the default). */
152130803Smarcel  return SVN_NO_ERROR;
153130803Smarcel}
154130803Smarcel
155130803Smarcel
156130803Smarcel/* Store as keys in CHANGED the paths of all node in ROOT that show a
157130803Smarcel * significant change.  "Significant" means that the text or
158130803Smarcel * properties of the node were changed, or that the node was added or
159130803Smarcel * deleted.
160130803Smarcel *
161130803Smarcel * The CHANGED hash set and its keys and values are allocated in POOL;
162130803Smarcel * keys are const char * paths and values are svn_log_changed_path_t.
163130803Smarcel *
164130803Smarcel * To prevent changes from being processed over and over again, the
165130803Smarcel * changed paths for ROOT may be passed in PREFETCHED_CHANGES.  If the
166130803Smarcel * latter is NULL, we will request the list inside this function.
167130803Smarcel *
168130803Smarcel * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
169130803Smarcel * AUTHZ_READ_BATON and FS) to check whether each changed-path (and
170130803Smarcel * copyfrom_path) is readable:
171130803Smarcel *
172130803Smarcel *     - If some paths are readable and some are not, then silently
173130803Smarcel *     omit the unreadable paths from the CHANGED hash, and return
174130803Smarcel *     SVN_ERR_AUTHZ_PARTIALLY_READABLE.
175130803Smarcel *
176130803Smarcel *     - If absolutely every changed-path (and copyfrom_path) is
177130803Smarcel *     unreadable, then return an empty CHANGED hash and
178130803Smarcel *     SVN_ERR_AUTHZ_UNREADABLE.  (This is to distinguish a revision
179130803Smarcel *     which truly has no changed paths from a revision in which all
180130803Smarcel *     paths are unreadable.)
181130803Smarcel */
182130803Smarcelstatic svn_error_t *
183130803Smarceldetect_changed(apr_hash_t **changed,
184130803Smarcel               svn_fs_root_t *root,
185130803Smarcel               svn_fs_t *fs,
186130803Smarcel               apr_hash_t *prefetched_changes,
187130803Smarcel               svn_repos_authz_func_t authz_read_func,
188130803Smarcel               void *authz_read_baton,
189130803Smarcel               apr_pool_t *pool)
190130803Smarcel{
191130803Smarcel  apr_hash_t *changes = prefetched_changes;
192130803Smarcel  apr_hash_index_t *hi;
193130803Smarcel  apr_pool_t *subpool;
194130803Smarcel  svn_boolean_t found_readable = FALSE;
195130803Smarcel  svn_boolean_t found_unreadable = FALSE;
196130803Smarcel
197130803Smarcel  *changed = svn_hash__make(pool);
198130803Smarcel  if (changes == NULL)
199130803Smarcel    SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
200130803Smarcel
201130803Smarcel  if (apr_hash_count(changes) == 0)
202130803Smarcel    /* No paths changed in this revision?  Uh, sure, I guess the
203130803Smarcel       revision is readable, then.  */
204130803Smarcel    return SVN_NO_ERROR;
205130803Smarcel
206130803Smarcel  subpool = svn_pool_create(pool);
207130803Smarcel
208130803Smarcel  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
209130803Smarcel    {
210130803Smarcel      /* NOTE:  Much of this loop is going to look quite similar to
211130803Smarcel         svn_repos_check_revision_access(), but we have to do more things
212130803Smarcel         here, so we'll live with the duplication. */
213130803Smarcel      const void *key;
214130803Smarcel      void *val;
215130803Smarcel      svn_fs_path_change2_t *change;
216130803Smarcel      const char *path;
217130803Smarcel      char action;
218130803Smarcel      svn_log_changed_path2_t *item;
219130803Smarcel
220130803Smarcel      svn_pool_clear(subpool);
221130803Smarcel
222130803Smarcel      /* KEY will be the path, VAL the change. */
223130803Smarcel      apr_hash_this(hi, &key, NULL, &val);
224130803Smarcel      path = (const char *) key;
225130803Smarcel      change = val;
226130803Smarcel
227130803Smarcel      /* Skip path if unreadable. */
228130803Smarcel      if (authz_read_func)
229130803Smarcel        {
230130803Smarcel          svn_boolean_t readable;
231130803Smarcel          SVN_ERR(authz_read_func(&readable,
232130803Smarcel                                  root, path,
233130803Smarcel                                  authz_read_baton, subpool));
234130803Smarcel          if (! readable)
235130803Smarcel            {
236130803Smarcel              found_unreadable = TRUE;
237130803Smarcel              continue;
238130803Smarcel            }
239130803Smarcel        }
240130803Smarcel
241130803Smarcel      /* At least one changed-path was readable. */
242130803Smarcel      found_readable = TRUE;
243130803Smarcel
244130803Smarcel      switch (change->change_kind)
245130803Smarcel        {
246130803Smarcel        case svn_fs_path_change_reset:
247130803Smarcel          continue;
248130803Smarcel
249130803Smarcel        case svn_fs_path_change_add:
250130803Smarcel          action = 'A';
251130803Smarcel          break;
252130803Smarcel
253130803Smarcel        case svn_fs_path_change_replace:
254130803Smarcel          action = 'R';
255130803Smarcel          break;
256130803Smarcel
257130803Smarcel        case svn_fs_path_change_delete:
258130803Smarcel          action = 'D';
259130803Smarcel          break;
260130803Smarcel
261130803Smarcel        case svn_fs_path_change_modify:
262130803Smarcel        default:
263130803Smarcel          action = 'M';
264130803Smarcel          break;
265130803Smarcel        }
266130803Smarcel
267130803Smarcel      item = svn_log_changed_path2_create(pool);
268130803Smarcel      item->action = action;
269130803Smarcel      item->node_kind = change->node_kind;
270130803Smarcel      item->copyfrom_rev = SVN_INVALID_REVNUM;
271130803Smarcel      item->text_modified = change->text_mod ? svn_tristate_true
272130803Smarcel                                             : svn_tristate_false;
273130803Smarcel      item->props_modified = change->prop_mod ? svn_tristate_true
274130803Smarcel                                              : svn_tristate_false;
275130803Smarcel
276130803Smarcel      /* Pre-1.6 revision files don't store the change path kind, so fetch
277130803Smarcel         it manually. */
278130803Smarcel      if (item->node_kind == svn_node_unknown)
279130803Smarcel        {
280130803Smarcel          svn_fs_root_t *check_root = root;
281130803Smarcel          const char *check_path = path;
282130803Smarcel
283130803Smarcel          /* Deleted items don't exist so check earlier revision.  We
284130803Smarcel             know the parent must exist and could be a copy */
285130803Smarcel          if (change->change_kind == svn_fs_path_change_delete)
286130803Smarcel            {
287130803Smarcel              svn_fs_history_t *history;
288130803Smarcel              svn_revnum_t prev_rev;
289130803Smarcel              const char *parent_path, *name;
290130803Smarcel
291130803Smarcel              svn_fspath__split(&parent_path, &name, path, subpool);
292130803Smarcel
293130803Smarcel              SVN_ERR(svn_fs_node_history(&history, root, parent_path,
294130803Smarcel                                          subpool));
295130803Smarcel
296130803Smarcel              /* Two calls because the first call returns the original
297130803Smarcel                 revision as the deleted child means it is 'interesting' */
298130803Smarcel              SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
299130803Smarcel              SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
300130803Smarcel
301130803Smarcel              SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev, history,
302130803Smarcel                                              subpool));
303130803Smarcel              SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev, subpool));
304130803Smarcel              check_path = svn_fspath__join(parent_path, name, subpool);
305130803Smarcel            }
306130803Smarcel
307130803Smarcel          SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path,
308130803Smarcel                                    subpool));
309130803Smarcel        }
310130803Smarcel
311130803Smarcel
312130803Smarcel      if ((action == 'A') || (action == 'R'))
313130803Smarcel        {
314130803Smarcel          const char *copyfrom_path = change->copyfrom_path;
315130803Smarcel          svn_revnum_t copyfrom_rev = change->copyfrom_rev;
316130803Smarcel
317130803Smarcel          /* the following is a potentially expensive operation since on FSFS
318130803Smarcel             we will follow the DAG from ROOT to PATH and that requires
319130803Smarcel             actually reading the directories along the way. */
320130803Smarcel          if (!change->copyfrom_known)
321130803Smarcel            SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
322130803Smarcel                                      root, path, subpool));
323130803Smarcel
324130803Smarcel          if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
325130803Smarcel            {
326130803Smarcel              svn_boolean_t readable = TRUE;
327130803Smarcel
328130803Smarcel              if (authz_read_func)
329130803Smarcel                {
330130803Smarcel                  svn_fs_root_t *copyfrom_root;
331130803Smarcel
332130803Smarcel                  SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
333130803Smarcel                                               copyfrom_rev, subpool));
334130803Smarcel                  SVN_ERR(authz_read_func(&readable,
335130803Smarcel                                          copyfrom_root, copyfrom_path,
336130803Smarcel                                          authz_read_baton, subpool));
337130803Smarcel                  if (! readable)
338130803Smarcel                    found_unreadable = TRUE;
339130803Smarcel                }
340130803Smarcel
341130803Smarcel              if (readable)
342130803Smarcel                {
343130803Smarcel                  item->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
344130803Smarcel                  item->copyfrom_rev = copyfrom_rev;
345130803Smarcel                }
346130803Smarcel            }
347130803Smarcel        }
348130803Smarcel      svn_hash_sets(*changed, apr_pstrdup(pool, path), item);
349130803Smarcel    }
350130803Smarcel
351130803Smarcel  svn_pool_destroy(subpool);
352130803Smarcel
353130803Smarcel  if (! found_readable)
354130803Smarcel    /* Every changed-path was unreadable. */
355130803Smarcel    return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE,
356130803Smarcel                            NULL, NULL);
357130803Smarcel
358130803Smarcel  if (found_unreadable)
359130803Smarcel    /* At least one changed-path was unreadable. */
360130803Smarcel    return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE,
361130803Smarcel                            NULL, NULL);
362130803Smarcel
363130803Smarcel  /* Every changed-path was readable. */
364130803Smarcel  return SVN_NO_ERROR;
365130803Smarcel}
366130803Smarcel
367130803Smarcel/* This is used by svn_repos_get_logs to keep track of multiple
368130803Smarcel * path history information while working through history.
369130803Smarcel *
370130803Smarcel * The two pools are swapped after each iteration through history because
371130803Smarcel * to get the next history requires the previous one.
372130803Smarcel */
373130803Smarcelstruct path_info
374130803Smarcel{
375130803Smarcel  svn_stringbuf_t *path;
376130803Smarcel  svn_revnum_t history_rev;
377130803Smarcel  svn_boolean_t done;
378130803Smarcel  svn_boolean_t first_time;
379130803Smarcel
380130803Smarcel  /* If possible, we like to keep open the history object for each path,
381130803Smarcel     since it avoids needed to open and close it many times as we walk
382130803Smarcel     backwards in time.  To do so we need two pools, so that we can clear
383130803Smarcel     one each time through.  If we're not holding the history open for
384130803Smarcel     this path then these three pointers will be NULL. */
385130803Smarcel  svn_fs_history_t *hist;
386130803Smarcel  apr_pool_t *newpool;
387130803Smarcel  apr_pool_t *oldpool;
388130803Smarcel};
389130803Smarcel
390130803Smarcel/* Advance to the next history for the path.
391130803Smarcel *
392130803Smarcel * If INFO->HIST is not NULL we do this using that existing history object,
393130803Smarcel * otherwise we open a new one.
394130803Smarcel *
395130803Smarcel * If no more history is available or the history revision is less
396130803Smarcel * (earlier) than START, or the history is not available due
397130803Smarcel * to authorization, then INFO->DONE is set to TRUE.
398130803Smarcel *
399130803Smarcel * A STRICT value of FALSE will indicate to follow history across copied
400130803Smarcel * paths.
401130803Smarcel *
402130803Smarcel * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
403130803Smarcel * AUTHZ_READ_BATON and FS) to check whether INFO->PATH is still readable if
404130803Smarcel * we do indeed find more history for the path.
405130803Smarcel */
406130803Smarcelstatic svn_error_t *
407130803Smarcelget_history(struct path_info *info,
408130803Smarcel            svn_fs_t *fs,
409130803Smarcel            svn_boolean_t strict,
410130803Smarcel            svn_repos_authz_func_t authz_read_func,
411130803Smarcel            void *authz_read_baton,
412130803Smarcel            svn_revnum_t start,
413130803Smarcel            apr_pool_t *pool)
414130803Smarcel{
415130803Smarcel  svn_fs_root_t *history_root = NULL;
416130803Smarcel  svn_fs_history_t *hist;
417130803Smarcel  apr_pool_t *subpool;
418130803Smarcel  const char *path;
419130803Smarcel
420130803Smarcel  if (info->hist)
421130803Smarcel    {
422130803Smarcel      subpool = info->newpool;
423130803Smarcel
424130803Smarcel      SVN_ERR(svn_fs_history_prev(&info->hist, info->hist, ! strict, subpool));
425130803Smarcel
426130803Smarcel      hist = info->hist;
427130803Smarcel    }
428130803Smarcel  else
429130803Smarcel    {
430130803Smarcel      subpool = svn_pool_create(pool);
431130803Smarcel
432130803Smarcel      /* Open the history located at the last rev we were at. */
433130803Smarcel      SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev,
434130803Smarcel                                   subpool));
435130803Smarcel
436130803Smarcel      SVN_ERR(svn_fs_node_history(&hist, history_root, info->path->data,
437130803Smarcel                                  subpool));
438130803Smarcel
439130803Smarcel      SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
440130803Smarcel
441130803Smarcel      if (info->first_time)
442130803Smarcel        info->first_time = FALSE;
443130803Smarcel      else
444130803Smarcel        SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
445130803Smarcel    }
446130803Smarcel
447130803Smarcel  if (! hist)
448130803Smarcel    {
449130803Smarcel      svn_pool_destroy(subpool);
450130803Smarcel      if (info->oldpool)
451130803Smarcel        svn_pool_destroy(info->oldpool);
452130803Smarcel      info->done = TRUE;
453130803Smarcel      return SVN_NO_ERROR;
454130803Smarcel    }
455130803Smarcel
456130803Smarcel  /* Fetch the location information for this history step. */
457130803Smarcel  SVN_ERR(svn_fs_history_location(&path, &info->history_rev,
458130803Smarcel                                  hist, subpool));
459130803Smarcel
460130803Smarcel  svn_stringbuf_set(info->path, path);
461130803Smarcel
462130803Smarcel  /* If this history item predates our START revision then
463130803Smarcel     don't fetch any more for this path. */
464130803Smarcel  if (info->history_rev < start)
465130803Smarcel    {
466130803Smarcel      svn_pool_destroy(subpool);
467130803Smarcel      if (info->oldpool)
468130803Smarcel        svn_pool_destroy(info->oldpool);
469130803Smarcel      info->done = TRUE;
470130803Smarcel      return SVN_NO_ERROR;
471130803Smarcel    }
472130803Smarcel
473130803Smarcel  /* Is the history item readable?  If not, done with path. */
474130803Smarcel  if (authz_read_func)
475130803Smarcel    {
476130803Smarcel      svn_boolean_t readable;
477130803Smarcel      SVN_ERR(svn_fs_revision_root(&history_root, fs,
478130803Smarcel                                   info->history_rev,
479130803Smarcel                                   subpool));
480130803Smarcel      SVN_ERR(authz_read_func(&readable, history_root,
481130803Smarcel                              info->path->data,
482130803Smarcel                              authz_read_baton,
483130803Smarcel                              subpool));
484130803Smarcel      if (! readable)
485130803Smarcel        info->done = TRUE;
486130803Smarcel    }
487130803Smarcel
488130803Smarcel  if (! info->hist)
489130803Smarcel    {
490130803Smarcel      svn_pool_destroy(subpool);
491130803Smarcel    }
492130803Smarcel  else
493130803Smarcel    {
494130803Smarcel      apr_pool_t *temppool = info->oldpool;
495130803Smarcel      info->oldpool = info->newpool;
496130803Smarcel      svn_pool_clear(temppool);
497130803Smarcel      info->newpool = temppool;
498130803Smarcel    }
499130803Smarcel
500130803Smarcel  return SVN_NO_ERROR;
501130803Smarcel}
502130803Smarcel
503130803Smarcel/* Set INFO->HIST to the next history for the path *if* there is history
504130803Smarcel * available and INFO->HISTORY_REV is equal to or greater than CURRENT.
505130803Smarcel *
506130803Smarcel * *CHANGED is set to TRUE if the path has history in the CURRENT revision,
507130803Smarcel * otherwise it is not touched.
508130803Smarcel *
509130803Smarcel * If we do need to get the next history revision for the path, call
510130803Smarcel * get_history to do it -- see it for details.
511130803Smarcel */
512130803Smarcelstatic svn_error_t *
513130803Smarcelcheck_history(svn_boolean_t *changed,
514130803Smarcel              struct path_info *info,
515130803Smarcel              svn_fs_t *fs,
516130803Smarcel              svn_revnum_t current,
517130803Smarcel              svn_boolean_t strict,
518130803Smarcel              svn_repos_authz_func_t authz_read_func,
519130803Smarcel              void *authz_read_baton,
520130803Smarcel              svn_revnum_t start,
521130803Smarcel              apr_pool_t *pool)
522130803Smarcel{
523130803Smarcel  /* If we're already done with histories for this path,
524130803Smarcel     don't try to fetch any more. */
525130803Smarcel  if (info->done)
526130803Smarcel    return SVN_NO_ERROR;
527130803Smarcel
528130803Smarcel  /* If the last rev we got for this path is less than CURRENT,
529130803Smarcel     then just return and don't fetch history for this path.
530130803Smarcel     The caller will get to this rev eventually or else reach
531130803Smarcel     the limit. */
532130803Smarcel  if (info->history_rev < current)
533130803Smarcel    return SVN_NO_ERROR;
534130803Smarcel
535130803Smarcel  /* If the last rev we got for this path is equal to CURRENT
536130803Smarcel     then set *CHANGED to true and get the next history
537130803Smarcel     rev where this path was changed. */
538130803Smarcel  *changed = TRUE;
539130803Smarcel  return get_history(info, fs, strict, authz_read_func,
540130803Smarcel                     authz_read_baton, start, pool);
541130803Smarcel}
542130803Smarcel
543130803Smarcel/* Return the next interesting revision in our list of HISTORIES. */
544130803Smarcelstatic svn_revnum_t
545130803Smarcelnext_history_rev(const apr_array_header_t *histories)
546130803Smarcel{
547130803Smarcel  svn_revnum_t next_rev = SVN_INVALID_REVNUM;
548130803Smarcel  int i;
549130803Smarcel
550130803Smarcel  for (i = 0; i < histories->nelts; ++i)
551130803Smarcel    {
552130803Smarcel      struct path_info *info = APR_ARRAY_IDX(histories, i,
553130803Smarcel                                             struct path_info *);
554130803Smarcel      if (info->done)
555130803Smarcel        continue;
556130803Smarcel      if (info->history_rev > next_rev)
557130803Smarcel        next_rev = info->history_rev;
558130803Smarcel    }
559130803Smarcel
560130803Smarcel  return next_rev;
561130803Smarcel}
562130803Smarcel
563130803Smarcel/* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to
564130803Smarcel   catalogs describing how mergeinfo values on paths (which are the
565130803Smarcel   keys of those catalogs) were changed in REV.  If *PREFETCHED_CAHNGES
566130803Smarcel   already contains the changed paths for REV, use that.  Otherwise,
567130803Smarcel   request that data and return it in *PREFETCHED_CHANGES. */
568130803Smarcel/* ### TODO: This would make a *great*, useful public function,
569130803Smarcel   ### svn_repos_fs_mergeinfo_changed()!  -- cmpilato  */
570130803Smarcelstatic svn_error_t *
571130803Smarcelfs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
572130803Smarcel                     svn_mergeinfo_catalog_t *added_mergeinfo_catalog,
573130803Smarcel                     apr_hash_t **prefetched_changes,
574130803Smarcel                     svn_fs_t *fs,
575130803Smarcel                     svn_revnum_t rev,
576130803Smarcel                     apr_pool_t *result_pool,
577130803Smarcel                     apr_pool_t *scratch_pool)
578130803Smarcel
579130803Smarcel{
580130803Smarcel  svn_fs_root_t *root;
581130803Smarcel  apr_pool_t *iterpool;
582130803Smarcel  apr_hash_index_t *hi;
583130803Smarcel
584130803Smarcel  /* Initialize return variables. */
585130803Smarcel  *deleted_mergeinfo_catalog = svn_hash__make(result_pool);
586130803Smarcel  *added_mergeinfo_catalog = svn_hash__make(result_pool);
587130803Smarcel
588130803Smarcel  /* Revision 0 has no mergeinfo and no mergeinfo changes. */
589130803Smarcel  if (rev == 0)
590130803Smarcel    return SVN_NO_ERROR;
591130803Smarcel
592130803Smarcel  /* We're going to use the changed-paths information for REV to
593130803Smarcel     narrow down our search. */
594130803Smarcel  SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
595130803Smarcel  if (*prefetched_changes == NULL)
596130803Smarcel    SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool));
597130803Smarcel
598130803Smarcel  /* No changed paths?  We're done. */
599130803Smarcel  if (apr_hash_count(*prefetched_changes) == 0)
600130803Smarcel    return SVN_NO_ERROR;
601130803Smarcel
602130803Smarcel  /* Loop over changes, looking for anything that might carry an
603130803Smarcel     svn:mergeinfo change and is one of our paths of interest, or a
604130803Smarcel     child or [grand]parent directory thereof. */
605130803Smarcel  iterpool = svn_pool_create(scratch_pool);
606130803Smarcel  for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
607130803Smarcel       hi;
608130803Smarcel       hi = apr_hash_next(hi))
609130803Smarcel    {
610130803Smarcel      const void *key;
611130803Smarcel      void *val;
612130803Smarcel      svn_fs_path_change2_t *change;
613130803Smarcel      const char *changed_path, *base_path = NULL;
614130803Smarcel      svn_revnum_t base_rev = SVN_INVALID_REVNUM;
615130803Smarcel      svn_fs_root_t *base_root = NULL;
616130803Smarcel      svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value;
617130803Smarcel
618130803Smarcel      svn_pool_clear(iterpool);
619130803Smarcel
620130803Smarcel      /* KEY will be the path, VAL the change. */
621130803Smarcel      apr_hash_this(hi, &key, NULL, &val);
622130803Smarcel      changed_path = key;
623130803Smarcel      change = val;
624130803Smarcel
625130803Smarcel      /* If there was no property change on this item, ignore it. */
626130803Smarcel      if (! change->prop_mod)
627130803Smarcel        continue;
628130803Smarcel
629130803Smarcel      switch (change->change_kind)
630130803Smarcel        {
631130803Smarcel
632130803Smarcel        /* ### TODO: Can the add, replace, and modify cases be joined
633130803Smarcel           ### together to all use svn_repos__prev_location()?  The
634130803Smarcel           ### difference would be the fallback case (path/rev-1 for
635130803Smarcel           ### modifies, NULL otherwise).  -- cmpilato  */
636130803Smarcel
637130803Smarcel        /* If the path was added or replaced, see if it was created via
638130803Smarcel           copy.  If so, that will tell us where its previous location
639130803Smarcel           was.  If not, there's no previous location to examine.  */
640130803Smarcel        case svn_fs_path_change_add:
641130803Smarcel        case svn_fs_path_change_replace:
642130803Smarcel          {
643130803Smarcel            const char *copyfrom_path;
644130803Smarcel            svn_revnum_t copyfrom_rev;
645130803Smarcel
646130803Smarcel            SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
647130803Smarcel                                       root, changed_path, iterpool));
648130803Smarcel            if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
649130803Smarcel              {
650130803Smarcel                base_path = apr_pstrdup(scratch_pool, copyfrom_path);
651130803Smarcel                base_rev = copyfrom_rev;
652130803Smarcel              }
653130803Smarcel            break;
654130803Smarcel          }
655130803Smarcel
656130803Smarcel        /* If the path was merely modified, see if its previous
657130803Smarcel           location was affected by a copy which happened in this
658130803Smarcel           revision before assuming it holds the same path it did the
659130803Smarcel           previous revision. */
660130803Smarcel        case svn_fs_path_change_modify:
661130803Smarcel          {
662130803Smarcel            svn_revnum_t appeared_rev;
663130803Smarcel
664130803Smarcel            SVN_ERR(svn_repos__prev_location(&appeared_rev, &base_path,
665130803Smarcel                                             &base_rev, fs, rev,
666130803Smarcel                                             changed_path, iterpool));
667130803Smarcel
668130803Smarcel            /* If this path isn't the result of a copy that occurred
669130803Smarcel               in this revision, we can find the previous version of
670130803Smarcel               it in REV - 1 at the same path. */
671130803Smarcel            if (! (base_path && SVN_IS_VALID_REVNUM(base_rev)
672130803Smarcel                   && (appeared_rev == rev)))
673130803Smarcel              {
674130803Smarcel                base_path = changed_path;
675130803Smarcel                base_rev = rev - 1;
676130803Smarcel              }
677130803Smarcel            break;
678130803Smarcel          }
679130803Smarcel
680130803Smarcel        /* We don't care about any of the other cases. */
681130803Smarcel        case svn_fs_path_change_delete:
682130803Smarcel        case svn_fs_path_change_reset:
683130803Smarcel        default:
684130803Smarcel          continue;
685130803Smarcel        }
686130803Smarcel
687130803Smarcel      /* If there was a base location, fetch its mergeinfo property value. */
688130803Smarcel      if (base_path && SVN_IS_VALID_REVNUM(base_rev))
689130803Smarcel        {
690130803Smarcel          SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, iterpool));
691130803Smarcel          SVN_ERR(svn_fs_node_prop(&prev_mergeinfo_value, base_root, base_path,
692130803Smarcel                                   SVN_PROP_MERGEINFO, iterpool));
693130803Smarcel        }
694130803Smarcel
695130803Smarcel      /* Now fetch the current (as of REV) mergeinfo property value. */
696130803Smarcel      SVN_ERR(svn_fs_node_prop(&mergeinfo_value, root, changed_path,
697130803Smarcel                               SVN_PROP_MERGEINFO, iterpool));
698130803Smarcel
699130803Smarcel      /* No mergeinfo on either the new or previous location?  Just
700130803Smarcel         skip it.  (If there *was* a change, it would have been in
701130803Smarcel         inherited mergeinfo only, which should be picked up by the
702130803Smarcel         iteration of this loop that finds the parent paths that
703130803Smarcel         really got changed.)  */
704130803Smarcel      if (! (mergeinfo_value || prev_mergeinfo_value))
705130803Smarcel        continue;
706130803Smarcel
707130803Smarcel      /* If mergeinfo was explicitly added or removed on this path, we
708130803Smarcel         need to check to see if that was a real semantic change of
709130803Smarcel         meaning.  So, fill in the "missing" mergeinfo value with the
710130803Smarcel         inherited mergeinfo for that path/revision.  */
711130803Smarcel      if (prev_mergeinfo_value && (! mergeinfo_value))
712130803Smarcel        {
713130803Smarcel          apr_array_header_t *query_paths =
714130803Smarcel            apr_array_make(iterpool, 1, sizeof(const char *));
715130803Smarcel          svn_mergeinfo_t tmp_mergeinfo;
716130803Smarcel          svn_mergeinfo_catalog_t tmp_catalog;
717130803Smarcel
718130803Smarcel          APR_ARRAY_PUSH(query_paths, const char *) = changed_path;
719130803Smarcel          SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root,
720130803Smarcel                                        query_paths, svn_mergeinfo_inherited,
721130803Smarcel                                        FALSE, TRUE, iterpool, iterpool));
722130803Smarcel          tmp_mergeinfo = svn_hash_gets(tmp_catalog, changed_path);
723130803Smarcel          if (tmp_mergeinfo)
724130803Smarcel            SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_value,
725130803Smarcel                                            tmp_mergeinfo,
726130803Smarcel                                            iterpool));
727130803Smarcel        }
728130803Smarcel      else if (mergeinfo_value && (! prev_mergeinfo_value)
729130803Smarcel               && base_path && SVN_IS_VALID_REVNUM(base_rev))
730130803Smarcel        {
731130803Smarcel          apr_array_header_t *query_paths =
732130803Smarcel            apr_array_make(iterpool, 1, sizeof(const char *));
733130803Smarcel          svn_mergeinfo_t tmp_mergeinfo;
734130803Smarcel          svn_mergeinfo_catalog_t tmp_catalog;
735130803Smarcel
736130803Smarcel          APR_ARRAY_PUSH(query_paths, const char *) = base_path;
737130803Smarcel          SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, base_root,
738130803Smarcel                                        query_paths, svn_mergeinfo_inherited,
739130803Smarcel                                        FALSE, TRUE, iterpool, iterpool));
740130803Smarcel          tmp_mergeinfo = svn_hash_gets(tmp_catalog, base_path);
741130803Smarcel          if (tmp_mergeinfo)
742130803Smarcel            SVN_ERR(svn_mergeinfo_to_string(&prev_mergeinfo_value,
743130803Smarcel                                            tmp_mergeinfo,
744130803Smarcel                                            iterpool));
745130803Smarcel        }
746130803Smarcel
747130803Smarcel      /* If the old and new mergeinfo differ in any way, store the
748130803Smarcel         before and after mergeinfo values in our return hashes. */
749130803Smarcel      if ((prev_mergeinfo_value && (! mergeinfo_value))
750130803Smarcel          || ((! prev_mergeinfo_value) && mergeinfo_value)
751130803Smarcel          || (prev_mergeinfo_value && mergeinfo_value
752130803Smarcel              && (! svn_string_compare(mergeinfo_value,
753130803Smarcel                                       prev_mergeinfo_value))))
754130803Smarcel        {
755130803Smarcel          svn_mergeinfo_t prev_mergeinfo = NULL, mergeinfo = NULL;
756130803Smarcel          svn_mergeinfo_t deleted, added;
757130803Smarcel          const char *hash_path;
758130803Smarcel
759130803Smarcel          if (mergeinfo_value)
760130803Smarcel            SVN_ERR(svn_mergeinfo_parse(&mergeinfo,
761130803Smarcel                                        mergeinfo_value->data, iterpool));
762130803Smarcel          if (prev_mergeinfo_value)
763130803Smarcel            SVN_ERR(svn_mergeinfo_parse(&prev_mergeinfo,
764130803Smarcel                                        prev_mergeinfo_value->data, iterpool));
765130803Smarcel          SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
766130803Smarcel                                      mergeinfo, FALSE, result_pool,
767130803Smarcel                                      iterpool));
768130803Smarcel
769130803Smarcel          /* Toss interesting stuff into our return catalogs. */
770130803Smarcel          hash_path = apr_pstrdup(result_pool, changed_path);
771130803Smarcel          svn_hash_sets(*deleted_mergeinfo_catalog, hash_path, deleted);
772130803Smarcel          svn_hash_sets(*added_mergeinfo_catalog, hash_path, added);
773130803Smarcel        }
774130803Smarcel    }
775130803Smarcel
776130803Smarcel  svn_pool_destroy(iterpool);
777130803Smarcel  return SVN_NO_ERROR;
778130803Smarcel}
779130803Smarcel
780130803Smarcel
781130803Smarcel/* Determine what (if any) mergeinfo for PATHS was modified in
782130803Smarcel   revision REV, returning the differences for added mergeinfo in
783130803Smarcel   *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO.
784130803Smarcel   If *PREFETCHED_CAHNGES already contains the changed paths for
785130803Smarcel   REV, use that.  Otherwise, request that data and return it in
786130803Smarcel   *PREFETCHED_CHANGES.
787130803Smarcel   Use POOL for all allocations. */
788130803Smarcelstatic svn_error_t *
789130803Smarcelget_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
790130803Smarcel                               svn_mergeinfo_t *deleted_mergeinfo,
791130803Smarcel                               apr_hash_t **prefetched_changes,
792130803Smarcel                               svn_fs_t *fs,
793130803Smarcel                               const apr_array_header_t *paths,
794130803Smarcel                               svn_revnum_t rev,
795130803Smarcel                               apr_pool_t *result_pool,
796130803Smarcel                               apr_pool_t *scratch_pool)
797130803Smarcel{
798130803Smarcel  svn_mergeinfo_catalog_t added_mergeinfo_catalog, deleted_mergeinfo_catalog;
799130803Smarcel  apr_hash_index_t *hi;
800130803Smarcel  svn_fs_root_t *root;
801130803Smarcel  apr_pool_t *iterpool;
802130803Smarcel  int i;
803130803Smarcel  svn_error_t *err;
804130803Smarcel
805130803Smarcel  /* Initialize return value. */
806130803Smarcel  *added_mergeinfo = svn_hash__make(result_pool);
807130803Smarcel  *deleted_mergeinfo = svn_hash__make(result_pool);
808130803Smarcel
809130803Smarcel  /* If we're asking about revision 0, there's no mergeinfo to be found. */
810130803Smarcel  if (rev == 0)
811130803Smarcel    return SVN_NO_ERROR;
812130803Smarcel
813130803Smarcel  /* No paths?  No mergeinfo. */
814130803Smarcel  if (! paths->nelts)
815130803Smarcel    return SVN_NO_ERROR;
816130803Smarcel
817130803Smarcel  /* Create a work subpool and get a root for REV. */
818130803Smarcel  SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
819130803Smarcel
820130803Smarcel  /* Fetch the mergeinfo changes for REV. */
821130803Smarcel  err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog,
822130803Smarcel                             &added_mergeinfo_catalog,
823130803Smarcel                             prefetched_changes,
824130803Smarcel                             fs, rev, scratch_pool, scratch_pool);
825130803Smarcel  if (err)
826130803Smarcel    {
827130803Smarcel      if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
828130803Smarcel        {
829130803Smarcel          /* Issue #3896: If invalid mergeinfo is encountered the
830130803Smarcel             best we can do is ignore it and act as if there were
831130803Smarcel             no mergeinfo modifications. */
832130803Smarcel          svn_error_clear(err);
833130803Smarcel          return SVN_NO_ERROR;
834130803Smarcel        }
835130803Smarcel      else
836130803Smarcel        {
837130803Smarcel          return svn_error_trace(err);
838130803Smarcel        }
839130803Smarcel    }
840130803Smarcel
841130803Smarcel  /* In most revisions, there will be no mergeinfo change at all. */
842130803Smarcel  if (   apr_hash_count(deleted_mergeinfo_catalog) == 0
843130803Smarcel      && apr_hash_count(added_mergeinfo_catalog) == 0)
844130803Smarcel    return SVN_NO_ERROR;
845130803Smarcel
846130803Smarcel  /* Check our PATHS for any changes to their inherited mergeinfo.
847130803Smarcel     (We deal with changes to mergeinfo directly *on* the paths in the
848130803Smarcel     following loop.)  */
849130803Smarcel  iterpool = svn_pool_create(scratch_pool);
850130803Smarcel  for (i = 0; i < paths->nelts; i++)
851130803Smarcel    {
852130803Smarcel      const char *path = APR_ARRAY_IDX(paths, i, const char *);
853130803Smarcel      const char *prev_path;
854130803Smarcel      apr_ssize_t klen;
855130803Smarcel      svn_revnum_t appeared_rev, prev_rev;
856130803Smarcel      svn_fs_root_t *prev_root;
857130803Smarcel      svn_mergeinfo_catalog_t catalog, inherited_catalog;
858130803Smarcel      svn_mergeinfo_t prev_mergeinfo, mergeinfo, deleted, added,
859130803Smarcel        prev_inherited_mergeinfo, inherited_mergeinfo;
860130803Smarcel      apr_array_header_t *query_paths;
861130803Smarcel
862130803Smarcel      svn_pool_clear(iterpool);
863130803Smarcel
864130803Smarcel      /* If this path is represented in the changed-mergeinfo hashes,
865130803Smarcel         we'll deal with it in the loop below. */
866130803Smarcel      if (svn_hash_gets(deleted_mergeinfo_catalog, path))
867130803Smarcel        continue;
868130803Smarcel
869130803Smarcel      /* Figure out what path/rev to compare against.  Ignore
870130803Smarcel         not-found errors returned by the filesystem.  */
871130803Smarcel      err = svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
872130803Smarcel                                     fs, rev, path, iterpool);
873130803Smarcel      if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
874130803Smarcel                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))
875130803Smarcel        {
876130803Smarcel          svn_error_clear(err);
877130803Smarcel          err = SVN_NO_ERROR;
878130803Smarcel          continue;
879130803Smarcel        }
880130803Smarcel      SVN_ERR(err);
881130803Smarcel
882130803Smarcel      /* If this path isn't the result of a copy that occurred in this
883130803Smarcel         revision, we can find the previous version of it in REV - 1
884130803Smarcel         at the same path. */
885130803Smarcel      if (! (prev_path && SVN_IS_VALID_REVNUM(prev_rev)
886130803Smarcel             && (appeared_rev == rev)))
887130803Smarcel        {
888130803Smarcel          prev_path = path;
889130803Smarcel          prev_rev = rev - 1;
890130803Smarcel        }
891130803Smarcel
892130803Smarcel      /* Fetch the previous mergeinfo (including inherited stuff) for
893130803Smarcel         this path.  Ignore not-found errors returned by the
894130803Smarcel         filesystem or invalid mergeinfo (Issue #3896).*/
895130803Smarcel      SVN_ERR(svn_fs_revision_root(&prev_root, fs, prev_rev, iterpool));
896130803Smarcel      query_paths = apr_array_make(iterpool, 1, sizeof(const char *));
897130803Smarcel      APR_ARRAY_PUSH(query_paths, const char *) = prev_path;
898130803Smarcel      err = svn_fs_get_mergeinfo2(&catalog, prev_root, query_paths,
899130803Smarcel                                  svn_mergeinfo_inherited, FALSE, TRUE,
900130803Smarcel                                  iterpool, iterpool);
901130803Smarcel      if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
902130803Smarcel                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
903130803Smarcel                  err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
904130803Smarcel        {
905130803Smarcel          svn_error_clear(err);
906130803Smarcel          err = SVN_NO_ERROR;
907130803Smarcel          continue;
908130803Smarcel        }
909130803Smarcel      SVN_ERR(err);
910130803Smarcel
911130803Smarcel      /* Issue #4022 'svn log -g interprets change in inherited mergeinfo due
912130803Smarcel         to move as a merge': A copy where the source and destination inherit
913130803Smarcel         mergeinfo from the same parent means the inherited mergeinfo of the
914130803Smarcel         source and destination will differ, but this diffrence is not
915130803Smarcel         indicative of a merge unless the mergeinfo on the inherited parent
916130803Smarcel         has actually changed.
917130803Smarcel
918130803Smarcel         To check for this we must fetch the "raw" previous inherited
919130803Smarcel         mergeinfo and the "raw" mergeinfo @REV then compare these. */
920130803Smarcel      SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, prev_root, query_paths,
921130803Smarcel                                    svn_mergeinfo_nearest_ancestor, FALSE,
922130803Smarcel                                    FALSE, /* adjust_inherited_mergeinfo */
923130803Smarcel                                    iterpool, iterpool));
924130803Smarcel
925130803Smarcel      klen = strlen(prev_path);
926130803Smarcel      prev_mergeinfo = apr_hash_get(catalog, prev_path, klen);
927130803Smarcel      prev_inherited_mergeinfo = apr_hash_get(inherited_catalog, prev_path, klen);
928130803Smarcel
929130803Smarcel      /* Fetch the current mergeinfo (as of REV, and including
930130803Smarcel         inherited stuff) for this path. */
931130803Smarcel      APR_ARRAY_IDX(query_paths, 0, const char *) = path;
932130803Smarcel      SVN_ERR(svn_fs_get_mergeinfo2(&catalog, root, query_paths,
933130803Smarcel                                    svn_mergeinfo_inherited, FALSE, TRUE,
934130803Smarcel                                    iterpool, iterpool));
935130803Smarcel
936130803Smarcel      /* Issue #4022 again, fetch the raw inherited mergeinfo. */
937130803Smarcel      SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, root, query_paths,
938130803Smarcel                                    svn_mergeinfo_nearest_ancestor, FALSE,
939130803Smarcel                                    FALSE, /* adjust_inherited_mergeinfo */
940130803Smarcel                                    iterpool, iterpool));
941130803Smarcel
942130803Smarcel      klen = strlen(path);
943130803Smarcel      mergeinfo = apr_hash_get(catalog, path, klen);
944130803Smarcel      inherited_mergeinfo = apr_hash_get(inherited_catalog, path, klen);
945130803Smarcel
946130803Smarcel      if (!prev_mergeinfo && !mergeinfo)
947130803Smarcel        continue;
948130803Smarcel
949130803Smarcel      /* Last bit of issue #4022 checking. */
950130803Smarcel      if (prev_inherited_mergeinfo && inherited_mergeinfo)
951130803Smarcel        {
952130803Smarcel          svn_boolean_t inherits_same_mergeinfo;
953130803Smarcel
954130803Smarcel          SVN_ERR(svn_mergeinfo__equals(&inherits_same_mergeinfo,
955130803Smarcel                                        prev_inherited_mergeinfo,
956130803Smarcel                                        inherited_mergeinfo,
957130803Smarcel                                        TRUE, iterpool));
958130803Smarcel          /* If a copy rather than an actual merge brought about an
959130803Smarcel             inherited mergeinfo change then we are finished. */
960130803Smarcel          if (inherits_same_mergeinfo)
961130803Smarcel            continue;
962130803Smarcel        }
963130803Smarcel      else
964130803Smarcel        {
965130803Smarcel          svn_boolean_t same_mergeinfo;
966130803Smarcel          SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo,
967130803Smarcel                                        prev_inherited_mergeinfo,
968130803Smarcel                                        FALSE,
969130803Smarcel                                        TRUE, iterpool));
970130803Smarcel          if (same_mergeinfo)
971130803Smarcel            continue;
972130803Smarcel        }
973130803Smarcel
974130803Smarcel      /* Compare, constrast, and combine the results. */
975130803Smarcel      SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
976130803Smarcel                                  mergeinfo, FALSE, result_pool, iterpool));
977130803Smarcel      SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, deleted,
978130803Smarcel                                   result_pool, iterpool));
979130803Smarcel      SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, added,
980130803Smarcel                                   result_pool, iterpool));
981130803Smarcel     }
982130803Smarcel
983130803Smarcel  /* Merge all the mergeinfos which are, or are children of, one of
984130803Smarcel     our paths of interest into one giant delta mergeinfo.  */
985130803Smarcel  for (hi = apr_hash_first(scratch_pool, added_mergeinfo_catalog);
986130803Smarcel       hi; hi = apr_hash_next(hi))
987130803Smarcel    {
988130803Smarcel      const void *key;
989130803Smarcel      apr_ssize_t klen;
990130803Smarcel      void *val;
991130803Smarcel      const char *changed_path;
992130803Smarcel      svn_mergeinfo_t added, deleted;
993130803Smarcel
994130803Smarcel      /* The path is the key, the mergeinfo delta is the value. */
995130803Smarcel      apr_hash_this(hi, &key, &klen, &val);
996130803Smarcel      changed_path = key;
997130803Smarcel      added = val;
998130803Smarcel
999130803Smarcel      for (i = 0; i < paths->nelts; i++)
1000130803Smarcel        {
1001130803Smarcel          const char *path = APR_ARRAY_IDX(paths, i, const char *);
1002130803Smarcel          if (! svn_fspath__skip_ancestor(path, changed_path))
1003130803Smarcel            continue;
1004130803Smarcel          svn_pool_clear(iterpool);
1005130803Smarcel          deleted = apr_hash_get(deleted_mergeinfo_catalog, key, klen);
1006130803Smarcel          SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo,
1007130803Smarcel                                       svn_mergeinfo_dup(deleted, result_pool),
1008130803Smarcel                                       result_pool, iterpool));
1009130803Smarcel          SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo,
1010130803Smarcel                                       svn_mergeinfo_dup(added, result_pool),
1011130803Smarcel                                       result_pool, iterpool));
1012130803Smarcel
1013130803Smarcel          break;
1014130803Smarcel        }
1015130803Smarcel    }
1016130803Smarcel
1017130803Smarcel  svn_pool_destroy(iterpool);
1018130803Smarcel  return SVN_NO_ERROR;
1019130803Smarcel}
1020130803Smarcel
1021130803Smarcel
1022130803Smarcel/* Fill LOG_ENTRY with history information in FS at REV. */
1023130803Smarcelstatic svn_error_t *
1024130803Smarcelfill_log_entry(svn_log_entry_t *log_entry,
1025130803Smarcel               svn_revnum_t rev,
1026130803Smarcel               svn_fs_t *fs,
1027130803Smarcel               apr_hash_t *prefetched_changes,
1028130803Smarcel               svn_boolean_t discover_changed_paths,
1029130803Smarcel               const apr_array_header_t *revprops,
1030130803Smarcel               svn_repos_authz_func_t authz_read_func,
1031130803Smarcel               void *authz_read_baton,
1032130803Smarcel               apr_pool_t *pool)
1033130803Smarcel{
1034130803Smarcel  apr_hash_t *r_props, *changed_paths = NULL;
1035130803Smarcel  svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
1036130803Smarcel
1037130803Smarcel  /* Discover changed paths if the user requested them
1038130803Smarcel     or if we need to check that they are readable. */
1039130803Smarcel  if ((rev > 0)
1040130803Smarcel      && (authz_read_func || discover_changed_paths))
1041130803Smarcel    {
1042130803Smarcel      svn_fs_root_t *newroot;
1043130803Smarcel      svn_error_t *patherr;
1044130803Smarcel
1045130803Smarcel      SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
1046130803Smarcel      patherr = detect_changed(&changed_paths,
1047130803Smarcel                               newroot, fs, prefetched_changes,
1048130803Smarcel                               authz_read_func, authz_read_baton,
1049130803Smarcel                               pool);
1050130803Smarcel
1051130803Smarcel      if (patherr
1052130803Smarcel          && patherr->apr_err == SVN_ERR_AUTHZ_UNREADABLE)
1053130803Smarcel        {
1054130803Smarcel          /* All changed-paths are unreadable, so clear all fields. */
1055130803Smarcel          svn_error_clear(patherr);
1056130803Smarcel          changed_paths = NULL;
1057130803Smarcel          get_revprops = FALSE;
1058130803Smarcel        }
1059130803Smarcel      else if (patherr
1060130803Smarcel               && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
1061130803Smarcel        {
1062130803Smarcel          /* At least one changed-path was unreadable, so censor all
1063130803Smarcel             but author and date.  (The unreadable paths are already
1064130803Smarcel             missing from the hash.) */
1065130803Smarcel          svn_error_clear(patherr);
1066130803Smarcel          censor_revprops = TRUE;
1067130803Smarcel        }
1068130803Smarcel      else if (patherr)
1069130803Smarcel        return patherr;
1070130803Smarcel
1071130803Smarcel      /* It may be the case that an authz func was passed in, but
1072130803Smarcel         the user still doesn't want to see any changed-paths. */
1073130803Smarcel      if (! discover_changed_paths)
1074130803Smarcel        changed_paths = NULL;
1075130803Smarcel    }
1076130803Smarcel
1077130803Smarcel  if (get_revprops)
1078130803Smarcel    {
1079130803Smarcel      /* User is allowed to see at least some revprops. */
1080130803Smarcel      SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
1081130803Smarcel      if (revprops == NULL)
1082130803Smarcel        {
1083130803Smarcel          /* Requested all revprops... */
1084130803Smarcel          if (censor_revprops)
1085130803Smarcel            {
1086130803Smarcel              /* ... but we can only return author/date. */
1087130803Smarcel              log_entry->revprops = svn_hash__make(pool);
1088130803Smarcel              svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1089130803Smarcel                            svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR));
1090130803Smarcel              svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
1091130803Smarcel                            svn_hash_gets(r_props, SVN_PROP_REVISION_DATE));
1092130803Smarcel            }
1093130803Smarcel          else
1094130803Smarcel            /* ... so return all we got. */
1095130803Smarcel            log_entry->revprops = r_props;
1096130803Smarcel        }
1097130803Smarcel      else
1098130803Smarcel        {
1099130803Smarcel          /* Requested only some revprops... */
1100130803Smarcel          int i;
1101130803Smarcel          for (i = 0; i < revprops->nelts; i++)
1102130803Smarcel            {
1103130803Smarcel              char *name = APR_ARRAY_IDX(revprops, i, char *);
1104130803Smarcel              svn_string_t *value = svn_hash_gets(r_props, name);
1105130803Smarcel              if (censor_revprops
1106130803Smarcel                  && !(strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0
1107130803Smarcel                       || strcmp(name, SVN_PROP_REVISION_DATE) == 0))
1108130803Smarcel                /* ... but we can only return author/date. */
1109130803Smarcel                continue;
1110130803Smarcel              if (log_entry->revprops == NULL)
1111130803Smarcel                log_entry->revprops = svn_hash__make(pool);
1112130803Smarcel              svn_hash_sets(log_entry->revprops, name, value);
1113130803Smarcel            }
1114130803Smarcel        }
1115130803Smarcel    }
1116130803Smarcel
1117130803Smarcel  log_entry->changed_paths = changed_paths;
1118130803Smarcel  log_entry->changed_paths2 = changed_paths;
1119130803Smarcel  log_entry->revision = rev;
1120130803Smarcel
1121130803Smarcel  return SVN_NO_ERROR;
1122130803Smarcel}
1123130803Smarcel
1124130803Smarcel/* Send a log message for REV to RECEIVER with its RECEIVER_BATON.
1125130803Smarcel
1126130803Smarcel   FS is used with REV to fetch the interesting history information,
1127130803Smarcel   such as changed paths, revprops, etc.
1128130803Smarcel
1129130803Smarcel   The detect_changed function is used if either AUTHZ_READ_FUNC is
1130130803Smarcel   not NULL, or if DISCOVER_CHANGED_PATHS is TRUE.  See it for details.
1131130803Smarcel
1132130803Smarcel   If DESCENDING_ORDER is true, send child messages in descending order.
1133130803Smarcel
1134130803Smarcel   If REVPROPS is NULL, retrieve all revision properties; else, retrieve
1135130803Smarcel   only the revision properties named by the (const char *) array elements
1136130803Smarcel   (i.e. retrieve none if the array is empty).
1137130803Smarcel
1138130803Smarcel   LOG_TARGET_HISTORY_AS_MERGEINFO, HANDLING_MERGED_REVISION, and
1139130803Smarcel   NESTED_MERGES are as per the arguments of the same name to DO_LOGS.  If
1140130803Smarcel   HANDLING_MERGED_REVISION is true and *all* changed paths within REV are
1141130803Smarcel   already represented in LOG_TARGET_HISTORY_AS_MERGEINFO, then don't send
1142130803Smarcel   the log message for REV.  If SUBTRACTIVE_MERGE is true, then REV was
1143130803Smarcel   reverse merged.
1144130803Smarcel
1145130803Smarcel   If HANDLING_MERGED_REVISIONS is FALSE then ignore NESTED_MERGES.  Otherwise
1146130803Smarcel   if NESTED_MERGES is not NULL and REV is contained in it, then don't send
1147130803Smarcel   the log for REV, otherwise send it normally and add REV to
1148130803Smarcel   NESTED_MERGES. */
1149130803Smarcelstatic svn_error_t *
1150130803Smarcelsend_log(svn_revnum_t rev,
1151130803Smarcel         svn_fs_t *fs,
1152130803Smarcel         apr_hash_t *prefetched_changes,
1153130803Smarcel         svn_mergeinfo_t log_target_history_as_mergeinfo,
1154130803Smarcel         apr_hash_t *nested_merges,
1155130803Smarcel         svn_boolean_t discover_changed_paths,
1156130803Smarcel         svn_boolean_t subtractive_merge,
1157130803Smarcel         svn_boolean_t handling_merged_revision,
1158130803Smarcel         const apr_array_header_t *revprops,
1159130803Smarcel         svn_boolean_t has_children,
1160130803Smarcel         svn_log_entry_receiver_t receiver,
1161130803Smarcel         void *receiver_baton,
1162130803Smarcel         svn_repos_authz_func_t authz_read_func,
1163130803Smarcel         void *authz_read_baton,
1164130803Smarcel         apr_pool_t *pool)
1165130803Smarcel{
1166130803Smarcel  svn_log_entry_t *log_entry;
1167130803Smarcel  /* Assume we want to send the log for REV. */
1168130803Smarcel  svn_boolean_t found_rev_of_interest = TRUE;
1169130803Smarcel
1170130803Smarcel  log_entry = svn_log_entry_create(pool);
1171130803Smarcel  SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes,
1172130803Smarcel                         discover_changed_paths || handling_merged_revision,
1173130803Smarcel                         revprops, authz_read_func, authz_read_baton,
1174130803Smarcel                         pool));
1175130803Smarcel  log_entry->has_children = has_children;
1176130803Smarcel  log_entry->subtractive_merge = subtractive_merge;
1177130803Smarcel
1178130803Smarcel  /* Is REV a merged revision that is already part of
1179130803Smarcel     LOG_TARGET_HISTORY_AS_MERGEINFO?  If so then there is no
1180130803Smarcel     need to send it, since it already was (or will be) sent. */
1181130803Smarcel  if (handling_merged_revision
1182130803Smarcel      && log_entry->changed_paths2
1183130803Smarcel      && log_target_history_as_mergeinfo
1184130803Smarcel      && apr_hash_count(log_target_history_as_mergeinfo))
1185130803Smarcel    {
1186130803Smarcel      apr_hash_index_t *hi;
1187130803Smarcel      apr_pool_t *subpool = svn_pool_create(pool);
1188130803Smarcel
1189130803Smarcel      /* REV was merged in, but it might already be part of the log target's
1190130803Smarcel         natural history, so change our starting assumption. */
1191130803Smarcel      found_rev_of_interest = FALSE;
1192130803Smarcel
1193130803Smarcel      /* Look at each changed path in REV. */
1194130803Smarcel      for (hi = apr_hash_first(subpool, log_entry->changed_paths2);
1195130803Smarcel           hi;
1196130803Smarcel           hi = apr_hash_next(hi))
1197130803Smarcel        {
1198130803Smarcel          svn_boolean_t path_is_in_history = FALSE;
1199130803Smarcel          const char *changed_path = svn__apr_hash_index_key(hi);
1200130803Smarcel          apr_hash_index_t *hi2;
1201130803Smarcel          apr_pool_t *inner_subpool = svn_pool_create(subpool);
1202130803Smarcel
1203130803Smarcel          /* Look at each path on the log target's mergeinfo. */
1204130803Smarcel          for (hi2 = apr_hash_first(inner_subpool,
1205130803Smarcel                                    log_target_history_as_mergeinfo);
1206130803Smarcel               hi2;
1207130803Smarcel               hi2 = apr_hash_next(hi2))
1208130803Smarcel            {
1209130803Smarcel              const char *mergeinfo_path =
1210130803Smarcel                svn__apr_hash_index_key(hi2);
1211130803Smarcel              svn_rangelist_t *rangelist =
1212130803Smarcel                svn__apr_hash_index_val(hi2);
1213130803Smarcel
1214130803Smarcel              /* Check whether CHANGED_PATH at revision REV is a child of
1215130803Smarcel                 a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
1216130803Smarcel              if (svn_fspath__skip_ancestor(mergeinfo_path, changed_path))
1217130803Smarcel                {
1218130803Smarcel                  int i;
1219130803Smarcel
1220130803Smarcel                  for (i = 0; i < rangelist->nelts; i++)
1221130803Smarcel                    {
1222130803Smarcel                      svn_merge_range_t *range =
1223130803Smarcel                        APR_ARRAY_IDX(rangelist, i,
1224130803Smarcel                                      svn_merge_range_t *);
1225130803Smarcel                      if (rev > range->start && rev <= range->end)
1226130803Smarcel                        {
1227130803Smarcel                          path_is_in_history = TRUE;
1228130803Smarcel                          break;
1229130803Smarcel                        }
1230130803Smarcel                    }
1231130803Smarcel                }
1232130803Smarcel              if (path_is_in_history)
1233130803Smarcel                break;
1234130803Smarcel            }
1235130803Smarcel          svn_pool_destroy(inner_subpool);
1236130803Smarcel
1237130803Smarcel          if (!path_is_in_history)
1238130803Smarcel            {
1239130803Smarcel              /* If even one path in LOG_ENTRY->CHANGED_PATHS2 is not part of
1240130803Smarcel                 LOG_TARGET_HISTORY_AS_MERGEINFO, then we want to send the
1241130803Smarcel                 log for REV. */
1242130803Smarcel              found_rev_of_interest = TRUE;
1243130803Smarcel              break;
1244130803Smarcel            }
1245130803Smarcel        }
1246130803Smarcel      svn_pool_destroy(subpool);
1247130803Smarcel    }
1248130803Smarcel
1249130803Smarcel  /* If we only got changed paths the sake of detecting redundant merged
1250130803Smarcel     revisions, then be sure we don't send that info to the receiver. */
1251130803Smarcel  if (!discover_changed_paths && handling_merged_revision)
1252130803Smarcel    log_entry->changed_paths = log_entry->changed_paths2 = NULL;
1253130803Smarcel
1254130803Smarcel  /* Send the entry to the receiver, unless it is a redundant merged
1255130803Smarcel     revision. */
1256130803Smarcel  if (found_rev_of_interest)
1257130803Smarcel    {
1258130803Smarcel      /* Is REV a merged revision we've already sent? */
1259130803Smarcel      if (nested_merges && handling_merged_revision)
1260130803Smarcel        {
1261130803Smarcel          svn_revnum_t *merged_rev = apr_hash_get(nested_merges, &rev,
1262130803Smarcel                                                  sizeof(svn_revnum_t *));
1263130803Smarcel
1264130803Smarcel          if (merged_rev)
1265130803Smarcel            {
1266130803Smarcel              /* We already sent REV. */
1267130803Smarcel              return SVN_NO_ERROR;
1268130803Smarcel            }
1269130803Smarcel          else
1270130803Smarcel            {
1271130803Smarcel              /* NESTED_REVS needs to last across all the send_log, do_logs,
1272130803Smarcel                 handle_merged_revisions() recursions, so use the pool it
1273130803Smarcel                 was created in at the top of the recursion. */
1274130803Smarcel              apr_pool_t *hash_pool = apr_hash_pool_get(nested_merges);
1275130803Smarcel              svn_revnum_t *long_lived_rev = apr_palloc(hash_pool,
1276130803Smarcel                                                        sizeof(svn_revnum_t));
1277130803Smarcel              *long_lived_rev = rev;
1278130803Smarcel              apr_hash_set(nested_merges, long_lived_rev,
1279130803Smarcel                           sizeof(svn_revnum_t *), long_lived_rev);
1280130803Smarcel            }
1281130803Smarcel        }
1282130803Smarcel
1283130803Smarcel      return (*receiver)(receiver_baton, log_entry, pool);
1284130803Smarcel    }
1285130803Smarcel  else
1286130803Smarcel    {
1287130803Smarcel      return SVN_NO_ERROR;
1288130803Smarcel    }
1289130803Smarcel}
1290130803Smarcel
1291130803Smarcel/* This controls how many history objects we keep open.  For any targets
1292130803Smarcel   over this number we have to open and close their histories as needed,
1293130803Smarcel   which is CPU intensive, but keeps us from using an unbounded amount of
1294130803Smarcel   memory. */
1295130803Smarcel#define MAX_OPEN_HISTORIES 32
1296130803Smarcel
1297130803Smarcel/* Get the histories for PATHS, and store them in *HISTORIES.
1298130803Smarcel
1299130803Smarcel   If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1300130803Smarcel   repository locations as fatal -- just ignore them.  */
1301130803Smarcelstatic svn_error_t *
1302130803Smarcelget_path_histories(apr_array_header_t **histories,
1303130803Smarcel                   svn_fs_t *fs,
1304130803Smarcel                   const apr_array_header_t *paths,
1305130803Smarcel                   svn_revnum_t hist_start,
1306130803Smarcel                   svn_revnum_t hist_end,
1307130803Smarcel                   svn_boolean_t strict_node_history,
1308130803Smarcel                   svn_boolean_t ignore_missing_locations,
1309130803Smarcel                   svn_repos_authz_func_t authz_read_func,
1310130803Smarcel                   void *authz_read_baton,
1311130803Smarcel                   apr_pool_t *pool)
1312130803Smarcel{
1313130803Smarcel  svn_fs_root_t *root;
1314130803Smarcel  apr_pool_t *iterpool;
1315130803Smarcel  svn_error_t *err;
1316130803Smarcel  int i;
1317130803Smarcel
1318130803Smarcel  /* Create a history object for each path so we can walk through
1319130803Smarcel     them all at the same time until we have all changes or LIMIT
1320130803Smarcel     is reached.
1321130803Smarcel
1322130803Smarcel     There is some pool fun going on due to the fact that we have
1323130803Smarcel     to hold on to the old pool with the history before we can
1324130803Smarcel     get the next history.
1325130803Smarcel  */
1326130803Smarcel  *histories = apr_array_make(pool, paths->nelts,
1327130803Smarcel                              sizeof(struct path_info *));
1328130803Smarcel
1329130803Smarcel  SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool));
1330130803Smarcel
1331130803Smarcel  iterpool = svn_pool_create(pool);
1332130803Smarcel  for (i = 0; i < paths->nelts; i++)
1333130803Smarcel    {
1334130803Smarcel      const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
1335130803Smarcel      struct path_info *info = apr_palloc(pool,
1336130803Smarcel                                          sizeof(struct path_info));
1337130803Smarcel
1338130803Smarcel      if (authz_read_func)
1339130803Smarcel        {
1340130803Smarcel          svn_boolean_t readable;
1341130803Smarcel
1342130803Smarcel          svn_pool_clear(iterpool);
1343130803Smarcel
1344130803Smarcel          SVN_ERR(authz_read_func(&readable, root, this_path,
1345130803Smarcel                                  authz_read_baton, iterpool));
1346130803Smarcel          if (! readable)
1347130803Smarcel            return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
1348130803Smarcel        }
1349130803Smarcel
1350130803Smarcel      info->path = svn_stringbuf_create(this_path, pool);
1351130803Smarcel      info->done = FALSE;
1352130803Smarcel      info->history_rev = hist_end;
1353130803Smarcel      info->first_time = TRUE;
1354130803Smarcel
1355130803Smarcel      if (i < MAX_OPEN_HISTORIES)
1356130803Smarcel        {
1357130803Smarcel          err = svn_fs_node_history(&info->hist, root, this_path, pool);
1358130803Smarcel          if (err
1359130803Smarcel              && ignore_missing_locations
1360130803Smarcel              && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1361130803Smarcel                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1362130803Smarcel                  err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1363130803Smarcel            {
1364130803Smarcel              svn_error_clear(err);
1365130803Smarcel              continue;
1366130803Smarcel            }
1367130803Smarcel          SVN_ERR(err);
1368130803Smarcel          info->newpool = svn_pool_create(pool);
1369130803Smarcel          info->oldpool = svn_pool_create(pool);
1370130803Smarcel        }
1371130803Smarcel      else
1372130803Smarcel        {
1373130803Smarcel          info->hist = NULL;
1374130803Smarcel          info->oldpool = NULL;
1375130803Smarcel          info->newpool = NULL;
1376130803Smarcel        }
1377130803Smarcel
1378130803Smarcel      err = get_history(info, fs,
1379130803Smarcel                        strict_node_history,
1380130803Smarcel                        authz_read_func, authz_read_baton,
1381130803Smarcel                        hist_start, pool);
1382130803Smarcel      if (err
1383130803Smarcel          && ignore_missing_locations
1384130803Smarcel          && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1385130803Smarcel              err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1386130803Smarcel              err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1387130803Smarcel        {
1388130803Smarcel          svn_error_clear(err);
1389130803Smarcel          continue;
1390130803Smarcel        }
1391130803Smarcel      SVN_ERR(err);
1392130803Smarcel      APR_ARRAY_PUSH(*histories, struct path_info *) = info;
1393130803Smarcel    }
1394130803Smarcel  svn_pool_destroy(iterpool);
1395130803Smarcel
1396130803Smarcel  return SVN_NO_ERROR;
1397130803Smarcel}
1398130803Smarcel
1399130803Smarcel/* Remove and return the first item from ARR. */
1400130803Smarcelstatic void *
1401130803Smarcelarray_pop_front(apr_array_header_t *arr)
1402130803Smarcel{
1403130803Smarcel  void *item = arr->elts;
1404130803Smarcel
1405130803Smarcel  if (apr_is_empty_array(arr))
1406130803Smarcel    return NULL;
1407130803Smarcel
1408130803Smarcel  arr->elts += arr->elt_size;
1409130803Smarcel  arr->nelts -= 1;
1410130803Smarcel  arr->nalloc -= 1;
1411130803Smarcel  return item;
1412130803Smarcel}
1413130803Smarcel
1414130803Smarcel/* A struct which represents a single revision range, and the paths which
1415130803Smarcel   have mergeinfo in that range. */
1416130803Smarcelstruct path_list_range
1417130803Smarcel{
1418130803Smarcel  apr_array_header_t *paths;
1419130803Smarcel  svn_merge_range_t range;
1420130803Smarcel
1421130803Smarcel  /* Is RANGE the result of a reverse merge? */
1422130803Smarcel  svn_boolean_t reverse_merge;
1423130803Smarcel};
1424130803Smarcel
1425130803Smarcel/* A struct which represents "inverse mergeinfo", that is, instead of having
1426130803Smarcel   a path->revision_range_list mapping, which is the way mergeinfo is commonly
1427130803Smarcel   represented, this struct enables a revision_range_list,path tuple, where
1428130803Smarcel   the paths can be accessed by revision. */
1429130803Smarcelstruct rangelist_path
1430130803Smarcel{
1431130803Smarcel  svn_rangelist_t *rangelist;
1432130803Smarcel  const char *path;
1433130803Smarcel};
1434130803Smarcel
1435130803Smarcel/* Comparator function for combine_mergeinfo_path_lists().  Sorts
1436130803Smarcel   rangelist_path structs in increasing order based upon starting revision,
1437130803Smarcel   then ending revision of the first element in the rangelist.
1438130803Smarcel
1439130803Smarcel   This does not sort rangelists based upon subsequent elements, only the
1440130803Smarcel   first range.  We'll sort any subsequent ranges in the correct order
1441130803Smarcel   when they get bumped up to the front by removal of earlier ones, so we
1442130803Smarcel   don't really have to sort them here.  See combine_mergeinfo_path_lists()
1443130803Smarcel   for details. */
1444130803Smarcelstatic int
1445130803Smarcelcompare_rangelist_paths(const void *a, const void *b)
1446130803Smarcel{
1447130803Smarcel  struct rangelist_path *rpa = *((struct rangelist_path *const *) a);
1448130803Smarcel  struct rangelist_path *rpb = *((struct rangelist_path *const *) b);
1449130803Smarcel  svn_merge_range_t *mra = APR_ARRAY_IDX(rpa->rangelist, 0,
1450130803Smarcel                                         svn_merge_range_t *);
1451130803Smarcel  svn_merge_range_t *mrb = APR_ARRAY_IDX(rpb->rangelist, 0,
1452130803Smarcel                                         svn_merge_range_t *);
1453130803Smarcel
1454130803Smarcel  if (mra->start < mrb->start)
1455130803Smarcel    return -1;
1456130803Smarcel  if (mra->start > mrb->start)
1457130803Smarcel    return 1;
1458130803Smarcel  if (mra->end < mrb->end)
1459130803Smarcel    return -1;
1460130803Smarcel  if (mra->end > mrb->end)
1461130803Smarcel    return 1;
1462130803Smarcel
1463130803Smarcel  return 0;
1464130803Smarcel}
1465130803Smarcel
1466130803Smarcel/* From MERGEINFO, return in *COMBINED_LIST, allocated in POOL, a list of
1467130803Smarcel   'struct path_list_range's.  This list represents the rangelists in
1468130803Smarcel   MERGEINFO and each path which has mergeinfo in that range.
1469130803Smarcel   If REVERSE_MERGE is true, then MERGEINFO represents mergeinfo removed
1470130803Smarcel   as the result of a reverse merge. */
1471130803Smarcelstatic svn_error_t *
1472130803Smarcelcombine_mergeinfo_path_lists(apr_array_header_t **combined_list,
1473130803Smarcel                             svn_mergeinfo_t mergeinfo,
1474130803Smarcel                             svn_boolean_t reverse_merge,
1475130803Smarcel                             apr_pool_t *pool)
1476130803Smarcel{
1477130803Smarcel  apr_hash_index_t *hi;
1478130803Smarcel  apr_array_header_t *rangelist_paths;
1479130803Smarcel  apr_pool_t *subpool = svn_pool_create(pool);
1480130803Smarcel
1481130803Smarcel  /* Create a list of (revision range, path) tuples from MERGEINFO. */
1482130803Smarcel  rangelist_paths = apr_array_make(subpool, apr_hash_count(mergeinfo),
1483130803Smarcel                                   sizeof(struct rangelist_path *));
1484130803Smarcel  for (hi = apr_hash_first(subpool, mergeinfo); hi;
1485130803Smarcel       hi = apr_hash_next(hi))
1486130803Smarcel    {
1487130803Smarcel      int i;
1488130803Smarcel      struct rangelist_path *rp = apr_palloc(subpool, sizeof(*rp));
1489130803Smarcel      apr_hash_this(hi, (void *) &rp->path, NULL,
1490130803Smarcel                    (void *) &rp->rangelist);
1491130803Smarcel      APR_ARRAY_PUSH(rangelist_paths, struct rangelist_path *) = rp;
1492130803Smarcel
1493130803Smarcel      /* We need to make local copies of the rangelist, since we will be
1494130803Smarcel         modifying it, below. */
1495130803Smarcel      rp->rangelist = svn_rangelist_dup(rp->rangelist, subpool);
1496130803Smarcel
1497130803Smarcel      /* Make all of the rangelists inclusive, both start and end. */
1498214947Sgonzo      for (i = 0; i < rp->rangelist->nelts; i++)
1499130803Smarcel        APR_ARRAY_IDX(rp->rangelist, i, svn_merge_range_t *)->start += 1;
1500130803Smarcel    }
1501130803Smarcel
1502130803Smarcel  /* Loop over the (revision range, path) tuples, chopping them into
1503130803Smarcel     (revision range, paths) tuples, and appending those to the output
1504130803Smarcel     list. */
1505130803Smarcel  if (! *combined_list)
1506130803Smarcel    *combined_list = apr_array_make(pool, 0, sizeof(struct path_list_range *));
1507130803Smarcel
1508130803Smarcel  while (rangelist_paths->nelts > 1)
1509130803Smarcel    {
1510130803Smarcel      svn_revnum_t youngest, next_youngest, tail, youngest_end;
1511130803Smarcel      struct path_list_range *plr;
1512130803Smarcel      struct rangelist_path *rp;
1513130803Smarcel      int num_revs;
1514130803Smarcel      int i;
1515130803Smarcel
1516130803Smarcel      /* First, sort the list such that the start revision of the first
1517214947Sgonzo         revision arrays are sorted. */
1518214947Sgonzo      qsort(rangelist_paths->elts, rangelist_paths->nelts,
1519130803Smarcel            rangelist_paths->elt_size, compare_rangelist_paths);
1520130803Smarcel
1521130803Smarcel      /* Next, find the number of revision ranges which start with the same
1522130803Smarcel         revision. */
1523130803Smarcel      rp = APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1524130803Smarcel      youngest =
1525130803Smarcel        APR_ARRAY_IDX(rp->rangelist, 0, struct svn_merge_range_t *)->start;
1526130803Smarcel      next_youngest = youngest;
1527130803Smarcel      for (num_revs = 1; next_youngest == youngest; num_revs++)
1528130803Smarcel        {
1529130803Smarcel          if (num_revs == rangelist_paths->nelts)
1530130803Smarcel            {
1531130803Smarcel              num_revs += 1;
1532130803Smarcel              break;
1533130803Smarcel            }
1534130803Smarcel          rp = APR_ARRAY_IDX(rangelist_paths, num_revs,
1535130803Smarcel                             struct rangelist_path *);
1536130803Smarcel          next_youngest = APR_ARRAY_IDX(rp->rangelist, 0,
1537130803Smarcel                                        struct svn_merge_range_t *)->start;
1538130803Smarcel        }
1539130803Smarcel      num_revs -= 1;
1540130803Smarcel
1541130803Smarcel      /* The start of the new range will be YOUNGEST, and we now find the end
1542130803Smarcel         of the new range, which should be either one less than the next
1543130803Smarcel         earliest start of a rangelist, or the end of the first rangelist. */
1544130803Smarcel      youngest_end =
1545130803Smarcel        APR_ARRAY_IDX(APR_ARRAY_IDX(rangelist_paths, 0,
1546130803Smarcel                                    struct rangelist_path *)->rangelist,
1547130803Smarcel                      0, svn_merge_range_t *)->end;
1548130803Smarcel      if ( (next_youngest == youngest) || (youngest_end < next_youngest) )
1549130803Smarcel        tail = youngest_end;
1550130803Smarcel      else
1551130803Smarcel        tail = next_youngest - 1;
1552130803Smarcel
1553130803Smarcel      /* Insert the (earliest, tail) tuple into the output list, along with
1554130803Smarcel         a list of paths which match it. */
1555130803Smarcel      plr = apr_palloc(pool, sizeof(*plr));
1556130803Smarcel      plr->reverse_merge = reverse_merge;
1557130803Smarcel      plr->range.start = youngest;
1558130803Smarcel      plr->range.end = tail;
1559130803Smarcel      plr->paths = apr_array_make(pool, num_revs, sizeof(const char *));
1560130803Smarcel      for (i = 0; i < num_revs; i++)
1561130803Smarcel        APR_ARRAY_PUSH(plr->paths, const char *) =
1562130803Smarcel          APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *)->path;
1563130803Smarcel      APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1564130803Smarcel
1565130803Smarcel      /* Now, check to see which (rangelist path) combinations we can remove,
1566130803Smarcel         and do so. */
1567130803Smarcel      for (i = 0; i < num_revs; i++)
1568130803Smarcel        {
1569130803Smarcel          svn_merge_range_t *range;
1570130803Smarcel          rp = APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *);
1571130803Smarcel          range = APR_ARRAY_IDX(rp->rangelist, 0, svn_merge_range_t *);
1572130803Smarcel
1573214947Sgonzo          /* Set the start of the range to beyond the end of the range we
1574130803Smarcel             just built.  If the range is now "inverted", we can get pop it
1575130803Smarcel             off the list. */
1576130803Smarcel          range->start = tail + 1;
1577130803Smarcel          if (range->start > range->end)
1578130803Smarcel            {
1579130803Smarcel              if (rp->rangelist->nelts == 1)
1580130803Smarcel                {
1581130803Smarcel                  /* The range is the only on its list, so we should remove
1582130803Smarcel                     the entire rangelist_path, adjusting our loop control
1583130803Smarcel                     variables appropriately. */
1584130803Smarcel                  array_pop_front(rangelist_paths);
1585130803Smarcel                  i--;
1586130803Smarcel                  num_revs--;
1587130803Smarcel                }
1588130803Smarcel              else
1589130803Smarcel                {
1590130803Smarcel                  /* We have more than one range on the list, so just remove
1591130803Smarcel                     the first one. */
1592130803Smarcel                  array_pop_front(rp->rangelist);
1593130803Smarcel                }
1594130803Smarcel            }
1595130803Smarcel        }
1596130803Smarcel    }
1597130803Smarcel
1598130803Smarcel  /* Finally, add the last remaining (revision range, path) to the output
1599130803Smarcel     list. */
1600130803Smarcel  if (rangelist_paths->nelts > 0)
1601130803Smarcel    {
1602130803Smarcel      struct rangelist_path *first_rp =
1603130803Smarcel        APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1604130803Smarcel      while (first_rp->rangelist->nelts > 0)
1605130803Smarcel        {
1606130803Smarcel          struct path_list_range *plr = apr_palloc(pool, sizeof(*plr));
1607130803Smarcel
1608130803Smarcel          plr->reverse_merge = reverse_merge;
1609130803Smarcel          plr->paths = apr_array_make(pool, 1, sizeof(const char *));
1610130803Smarcel          APR_ARRAY_PUSH(plr->paths, const char *) = first_rp->path;
1611130803Smarcel          plr->range = *APR_ARRAY_IDX(first_rp->rangelist, 0,
1612130803Smarcel                                      svn_merge_range_t *);
1613130803Smarcel          array_pop_front(first_rp->rangelist);
1614130803Smarcel          APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1615130803Smarcel        }
1616130803Smarcel    }
1617130803Smarcel
1618130803Smarcel  svn_pool_destroy(subpool);
1619130803Smarcel
1620130803Smarcel  return SVN_NO_ERROR;
1621130803Smarcel}
1622130803Smarcel
1623130803Smarcel
1624130803Smarcel/* Pity that C is so ... linear. */
1625130803Smarcelstatic svn_error_t *
1626130803Smarceldo_logs(svn_fs_t *fs,
1627130803Smarcel        const apr_array_header_t *paths,
1628130803Smarcel        svn_mergeinfo_t log_target_history_as_mergeinfo,
1629130803Smarcel        svn_mergeinfo_t processed,
1630130803Smarcel        apr_hash_t *nested_merges,
1631130803Smarcel        svn_revnum_t hist_start,
1632130803Smarcel        svn_revnum_t hist_end,
1633130803Smarcel        int limit,
1634130803Smarcel        svn_boolean_t discover_changed_paths,
1635130803Smarcel        svn_boolean_t strict_node_history,
1636130803Smarcel        svn_boolean_t include_merged_revisions,
1637130803Smarcel        svn_boolean_t handling_merged_revisions,
1638130803Smarcel        svn_boolean_t subtractive_merge,
1639130803Smarcel        svn_boolean_t ignore_missing_locations,
1640130803Smarcel        const apr_array_header_t *revprops,
1641130803Smarcel        svn_boolean_t descending_order,
1642130803Smarcel        svn_log_entry_receiver_t receiver,
1643130803Smarcel        void *receiver_baton,
1644130803Smarcel        svn_repos_authz_func_t authz_read_func,
1645130803Smarcel        void *authz_read_baton,
1646130803Smarcel        apr_pool_t *pool);
1647130803Smarcel
1648130803Smarcel/* Comparator function for handle_merged_revisions().  Sorts path_list_range
1649130803Smarcel   structs in increasing order based on the struct's RANGE.START revision,
1650130803Smarcel   then RANGE.END revision. */
1651130803Smarcelstatic int
1652130803Smarcelcompare_path_list_range(const void *a, const void *b)
1653130803Smarcel{
1654130803Smarcel  struct path_list_range *plr_a = *((struct path_list_range *const *) a);
1655130803Smarcel  struct path_list_range *plr_b = *((struct path_list_range *const *) b);
1656130803Smarcel
1657130803Smarcel  if (plr_a->range.start < plr_b->range.start)
1658130803Smarcel    return -1;
1659130803Smarcel  if (plr_a->range.start > plr_b->range.start)
1660130803Smarcel    return 1;
1661130803Smarcel  if (plr_a->range.end < plr_b->range.end)
1662130803Smarcel    return -1;
1663130803Smarcel  if (plr_a->range.end > plr_b->range.end)
1664130803Smarcel    return 1;
1665130803Smarcel
1666130803Smarcel  return 0;
1667130803Smarcel}
1668130803Smarcel
1669130803Smarcel/* Examine the ADDED_MERGEINFO and DELETED_MERGEINFO for revision REV in FS
1670130803Smarcel   (as collected by examining paths of interest to a log operation), and
1671130803Smarcel   determine which revisions to report as having been merged or reverse-merged
1672130803Smarcel   via the commit resulting in REV.
1673130803Smarcel
1674130803Smarcel   Silently ignore some failures to find the revisions mentioned in the
1675130803Smarcel   added/deleted mergeinfos, as might happen if there is invalid mergeinfo.
1676130803Smarcel
1677130803Smarcel   Other parameters are as described by do_logs(), around which this
1678130803Smarcel   is a recursion wrapper. */
1679130803Smarcelstatic svn_error_t *
1680130803Smarcelhandle_merged_revisions(svn_revnum_t rev,
1681130803Smarcel                        svn_fs_t *fs,
1682130803Smarcel                        svn_mergeinfo_t log_target_history_as_mergeinfo,
1683130803Smarcel                        apr_hash_t *nested_merges,
1684130803Smarcel                        svn_mergeinfo_t processed,
1685130803Smarcel                        svn_mergeinfo_t added_mergeinfo,
1686130803Smarcel                        svn_mergeinfo_t deleted_mergeinfo,
1687130803Smarcel                        svn_boolean_t discover_changed_paths,
1688130803Smarcel                        svn_boolean_t strict_node_history,
1689130803Smarcel                        const apr_array_header_t *revprops,
1690130803Smarcel                        svn_log_entry_receiver_t receiver,
1691130803Smarcel                        void *receiver_baton,
1692130803Smarcel                        svn_repos_authz_func_t authz_read_func,
1693130803Smarcel                        void *authz_read_baton,
1694130803Smarcel                        apr_pool_t *pool)
1695130803Smarcel{
1696130803Smarcel  apr_array_header_t *combined_list = NULL;
1697130803Smarcel  svn_log_entry_t *empty_log_entry;
1698130803Smarcel  apr_pool_t *iterpool;
1699130803Smarcel  int i;
1700130803Smarcel
1701130803Smarcel  if (apr_hash_count(added_mergeinfo) == 0
1702130803Smarcel      && apr_hash_count(deleted_mergeinfo) == 0)
1703130803Smarcel    return SVN_NO_ERROR;
1704130803Smarcel
1705130803Smarcel  if (apr_hash_count(added_mergeinfo))
1706130803Smarcel    SVN_ERR(combine_mergeinfo_path_lists(&combined_list, added_mergeinfo,
1707130803Smarcel                                          FALSE, pool));
1708130803Smarcel
1709130803Smarcel  if (apr_hash_count(deleted_mergeinfo))
1710130803Smarcel    SVN_ERR(combine_mergeinfo_path_lists(&combined_list, deleted_mergeinfo,
1711130803Smarcel                                          TRUE, pool));
1712130803Smarcel
1713130803Smarcel  SVN_ERR_ASSERT(combined_list != NULL);
1714130803Smarcel  qsort(combined_list->elts, combined_list->nelts,
1715130803Smarcel        combined_list->elt_size, compare_path_list_range);
1716130803Smarcel
1717130803Smarcel  /* Because the combined_lists are ordered youngest to oldest,
1718130803Smarcel     iterate over them in reverse. */
1719130803Smarcel  iterpool = svn_pool_create(pool);
1720130803Smarcel  for (i = combined_list->nelts - 1; i >= 0; i--)
1721130803Smarcel    {
1722130803Smarcel      struct path_list_range *pl_range
1723130803Smarcel        = APR_ARRAY_IDX(combined_list, i, struct path_list_range *);
1724130803Smarcel
1725130803Smarcel      svn_pool_clear(iterpool);
1726130803Smarcel      SVN_ERR(do_logs(fs, pl_range->paths, log_target_history_as_mergeinfo,
1727130803Smarcel                      processed, nested_merges,
1728130803Smarcel                      pl_range->range.start, pl_range->range.end, 0,
1729130803Smarcel                      discover_changed_paths, strict_node_history,
1730130803Smarcel                      TRUE, pl_range->reverse_merge, TRUE, TRUE,
1731130803Smarcel                      revprops, TRUE, receiver, receiver_baton,
1732130803Smarcel                      authz_read_func, authz_read_baton, iterpool));
1733130803Smarcel    }
1734130803Smarcel  svn_pool_destroy(iterpool);
1735130803Smarcel
1736130803Smarcel  /* Send the empty revision.  */
1737130803Smarcel  empty_log_entry = svn_log_entry_create(pool);
1738130803Smarcel  empty_log_entry->revision = SVN_INVALID_REVNUM;
1739130803Smarcel  return (*receiver)(receiver_baton, empty_log_entry, pool);
1740130803Smarcel}
1741130803Smarcel
1742130803Smarcel/* This is used by do_logs to differentiate between forward and
1743130803Smarcel   reverse merges. */
1744130803Smarcelstruct added_deleted_mergeinfo
1745130803Smarcel{
1746130803Smarcel  svn_mergeinfo_t added_mergeinfo;
1747130803Smarcel  svn_mergeinfo_t deleted_mergeinfo;
1748130803Smarcel};
1749130803Smarcel
1750130803Smarcel/* Reduce the search range PATHS, HIST_START, HIST_END by removing
1751130803Smarcel   parts already covered by PROCESSED.  If reduction is possible
1752130803Smarcel   elements may be removed from PATHS and *START_REDUCED and
1753130803Smarcel   *END_REDUCED may be set to a narrower range. */
1754130803Smarcelstatic svn_error_t *
1755130803Smarcelreduce_search(apr_array_header_t *paths,
1756130803Smarcel              svn_revnum_t *hist_start,
1757130803Smarcel              svn_revnum_t *hist_end,
1758130803Smarcel              svn_mergeinfo_t processed,
1759130803Smarcel              apr_pool_t *scratch_pool)
1760130803Smarcel{
1761130803Smarcel  /* We add 1 to end to compensate for store_search */
1762130803Smarcel  svn_revnum_t start = *hist_start <= *hist_end ? *hist_start : *hist_end;
1763130803Smarcel  svn_revnum_t end = *hist_start <= *hist_end ? *hist_end + 1 : *hist_start + 1;
1764130803Smarcel  int i;
1765130803Smarcel
1766130803Smarcel  for (i = 0; i < paths->nelts; ++i)
1767130803Smarcel    {
1768130803Smarcel      const char *path = APR_ARRAY_IDX(paths, i, const char *);
1769130803Smarcel      svn_rangelist_t *ranges = svn_hash_gets(processed, path);
1770130803Smarcel      int j;
1771130803Smarcel
1772130803Smarcel      if (!ranges)
1773130803Smarcel        continue;
1774130803Smarcel
1775130803Smarcel      /* ranges is ordered, could we use some sort of binary search
1776130803Smarcel         rather than iterating? */
1777130803Smarcel      for (j = 0; j < ranges->nelts; ++j)
1778130803Smarcel        {
1779130803Smarcel          svn_merge_range_t *range = APR_ARRAY_IDX(ranges, j,
1780130803Smarcel                                                   svn_merge_range_t *);
1781130803Smarcel          if (range->start <= start && range->end >= end)
1782130803Smarcel            {
1783130803Smarcel              for (j = i; j < paths->nelts - 1; ++j)
1784130803Smarcel                APR_ARRAY_IDX(paths, j, const char *)
1785130803Smarcel                  = APR_ARRAY_IDX(paths, j + 1, const char *);
1786130803Smarcel
1787130803Smarcel              --paths->nelts;
1788130803Smarcel              --i;
1789130803Smarcel              break;
1790130803Smarcel            }
1791130803Smarcel
1792130803Smarcel          /* If there is only one path then we also check for a
1793130803Smarcel             partial overlap rather than the full overlap above, and
1794130803Smarcel             reduce the [hist_start, hist_end] range rather than
1795130803Smarcel             dropping the path. */
1796130803Smarcel          if (paths->nelts == 1)
1797130803Smarcel            {
1798130803Smarcel              if (range->start <= start && range->end > start)
1799130803Smarcel                {
1800130803Smarcel                  if (start == *hist_start)
1801130803Smarcel                    *hist_start = range->end - 1;
1802130803Smarcel                  else
1803130803Smarcel                    *hist_end = range->end - 1;
1804130803Smarcel                  break;
1805130803Smarcel                }
1806130803Smarcel              if (range->start < end && range->end >= end)
1807130803Smarcel                {
1808130803Smarcel                  if (start == *hist_start)
1809130803Smarcel                    *hist_end = range->start;
1810130803Smarcel                  else
1811130803Smarcel                    *hist_start = range->start;
1812130803Smarcel                  break;
1813130803Smarcel                }
1814130803Smarcel            }
1815130803Smarcel        }
1816130803Smarcel    }
1817130803Smarcel
1818130803Smarcel  return SVN_NO_ERROR;
1819130803Smarcel}
1820130803Smarcel
1821130803Smarcel/* Extend PROCESSED to cover PATHS from HIST_START to HIST_END */
1822130803Smarcelstatic svn_error_t *
1823130803Smarcelstore_search(svn_mergeinfo_t processed,
1824130803Smarcel             const apr_array_header_t *paths,
1825130803Smarcel             svn_revnum_t hist_start,
1826130803Smarcel             svn_revnum_t hist_end,
1827130803Smarcel             apr_pool_t *scratch_pool)
1828130803Smarcel{
1829130803Smarcel  /* We add 1 to end so that we can use the mergeinfo API to handle
1830130803Smarcel     singe revisions where HIST_START is equal to HIST_END. */
1831130803Smarcel  svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end;
1832130803Smarcel  svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1;
1833130803Smarcel  svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool);
1834130803Smarcel  apr_pool_t *processed_pool = apr_hash_pool_get(processed);
1835130803Smarcel  int i;
1836130803Smarcel
1837130803Smarcel  for (i = 0; i < paths->nelts; ++i)
1838130803Smarcel    {
1839130803Smarcel      const char *path = APR_ARRAY_IDX(paths, i, const char *);
1840130803Smarcel      svn_rangelist_t *ranges = apr_array_make(processed_pool, 1,
1841130803Smarcel                                               sizeof(svn_merge_range_t*));
1842130803Smarcel      svn_merge_range_t *range = apr_palloc(processed_pool,
1843130803Smarcel                                            sizeof(svn_merge_range_t));
1844130803Smarcel
1845130803Smarcel      range->start = start;
1846130803Smarcel      range->end = end;
1847130803Smarcel      range->inheritable = TRUE;
1848130803Smarcel      APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range;
1849130803Smarcel      svn_hash_sets(mergeinfo, apr_pstrdup(processed_pool, path), ranges);
1850130803Smarcel    }
1851130803Smarcel  SVN_ERR(svn_mergeinfo_merge2(processed, mergeinfo,
1852130803Smarcel                               apr_hash_pool_get(processed), scratch_pool));
1853130803Smarcel
1854130803Smarcel  return SVN_NO_ERROR;
1855130803Smarcel}
1856130803Smarcel
1857130803Smarcel/* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke
1858130803Smarcel   RECEIVER with RECEIVER_BATON on them.  If DESCENDING_ORDER is TRUE, send
1859130803Smarcel   the logs back as we find them, else buffer the logs and send them back
1860130803Smarcel   in youngest->oldest order.
1861130803Smarcel
1862130803Smarcel   If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1863130803Smarcel   repository locations as fatal -- just ignore them.
1864130803Smarcel
1865130803Smarcel   If LOG_TARGET_HISTORY_AS_MERGEINFO is not NULL then it contains mergeinfo
1866130803Smarcel   representing the history of PATHS between HIST_START and HIST_END.
1867130803Smarcel
1868130803Smarcel   If HANDLING_MERGED_REVISIONS is TRUE then this is a recursive call for
1869130803Smarcel   merged revisions, see INCLUDE_MERGED_REVISIONS argument to
1870130803Smarcel   svn_repos_get_logs4().  If SUBTRACTIVE_MERGE is true, then this is a
1871130803Smarcel   recursive call for reverse merged revisions.
1872130803Smarcel
1873130803Smarcel   If NESTED_MERGES is not NULL then it is a hash of revisions (svn_revnum_t *
1874130803Smarcel   mapped to svn_revnum_t *) for logs that were previously sent.  On the first
1875130803Smarcel   call to do_logs it should always be NULL.  If INCLUDE_MERGED_REVISIONS is
1876130803Smarcel   TRUE, then NESTED_MERGES will be created on the first call to do_logs,
1877130803Smarcel   allocated in POOL.  It is then shared across
1878130803Smarcel   do_logs()/send_logs()/handle_merge_revisions() recursions, see also the
1879130803Smarcel   argument of the same name in send_logs().
1880130803Smarcel
1881130803Smarcel   PROCESSED is a mergeinfo hash that represents the paths and
1882130803Smarcel   revisions that have already been searched.  Allocated like
1883130803Smarcel   NESTED_MERGES above.
1884130803Smarcel
1885130803Smarcel   All other parameters are the same as svn_repos_get_logs4().
1886130803Smarcel */
1887130803Smarcelstatic svn_error_t *
1888130803Smarceldo_logs(svn_fs_t *fs,
1889130803Smarcel        const apr_array_header_t *paths,
1890130803Smarcel        svn_mergeinfo_t log_target_history_as_mergeinfo,
1891130803Smarcel        svn_mergeinfo_t processed,
1892130803Smarcel        apr_hash_t *nested_merges,
1893130803Smarcel        svn_revnum_t hist_start,
1894130803Smarcel        svn_revnum_t hist_end,
1895130803Smarcel        int limit,
1896130803Smarcel        svn_boolean_t discover_changed_paths,
1897130803Smarcel        svn_boolean_t strict_node_history,
1898130803Smarcel        svn_boolean_t include_merged_revisions,
1899130803Smarcel        svn_boolean_t subtractive_merge,
1900130803Smarcel        svn_boolean_t handling_merged_revisions,
1901130803Smarcel        svn_boolean_t ignore_missing_locations,
1902130803Smarcel        const apr_array_header_t *revprops,
1903130803Smarcel        svn_boolean_t descending_order,
1904130803Smarcel        svn_log_entry_receiver_t receiver,
1905130803Smarcel        void *receiver_baton,
1906130803Smarcel        svn_repos_authz_func_t authz_read_func,
1907130803Smarcel        void *authz_read_baton,
1908130803Smarcel        apr_pool_t *pool)
1909130803Smarcel{
1910130803Smarcel  apr_pool_t *iterpool;
1911130803Smarcel  apr_pool_t *subpool = NULL;
1912130803Smarcel  apr_array_header_t *revs = NULL;
1913130803Smarcel  apr_hash_t *rev_mergeinfo = NULL;
1914130803Smarcel  svn_revnum_t current;
1915130803Smarcel  apr_array_header_t *histories;
1916130803Smarcel  svn_boolean_t any_histories_left = TRUE;
1917130803Smarcel  int send_count = 0;
1918130803Smarcel  int i;
1919130803Smarcel
1920130803Smarcel  if (processed)
1921130803Smarcel    {
1922130803Smarcel      /* Casting away const. This only happens on recursive calls when
1923130803Smarcel         it is known to be safe because we allocated paths. */
1924130803Smarcel      SVN_ERR(reduce_search((apr_array_header_t *)paths, &hist_start, &hist_end,
1925130803Smarcel                            processed, pool));
1926130803Smarcel    }
1927130803Smarcel
1928130803Smarcel  if (!paths->nelts)
1929130803Smarcel    return SVN_NO_ERROR;
1930130803Smarcel
1931130803Smarcel  if (processed)
1932130803Smarcel    SVN_ERR(store_search(processed, paths, hist_start, hist_end, pool));
1933130803Smarcel
1934130803Smarcel  /* We have a list of paths and a revision range.  But we don't care
1935130803Smarcel     about all the revisions in the range -- only the ones in which
1936130803Smarcel     one of our paths was changed.  So let's go figure out which
1937130803Smarcel     revisions contain real changes to at least one of our paths.  */
1938130803Smarcel  SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end,
1939130803Smarcel                             strict_node_history, ignore_missing_locations,
1940130803Smarcel                             authz_read_func, authz_read_baton, pool));
1941130803Smarcel
1942130803Smarcel  /* Loop through all the revisions in the range and add any
1943130803Smarcel     where a path was changed to the array, or if they wanted
1944130803Smarcel     history in reverse order just send it to them right away. */
1945130803Smarcel  iterpool = svn_pool_create(pool);
1946130803Smarcel  for (current = hist_end;
1947130803Smarcel       any_histories_left;
1948130803Smarcel       current = next_history_rev(histories))
1949130803Smarcel    {
1950130803Smarcel      svn_boolean_t changed = FALSE;
1951130803Smarcel      any_histories_left = FALSE;
1952130803Smarcel      svn_pool_clear(iterpool);
1953130803Smarcel
1954130803Smarcel      for (i = 0; i < histories->nelts; i++)
1955130803Smarcel        {
1956130803Smarcel          struct path_info *info = APR_ARRAY_IDX(histories, i,
1957130803Smarcel                                                 struct path_info *);
1958130803Smarcel
1959130803Smarcel          /* Check history for this path in current rev. */
1960130803Smarcel          SVN_ERR(check_history(&changed, info, fs, current,
1961130803Smarcel                                strict_node_history, authz_read_func,
1962130803Smarcel                                authz_read_baton, hist_start, pool));
1963130803Smarcel          if (! info->done)
1964130803Smarcel            any_histories_left = TRUE;
1965130803Smarcel        }
1966130803Smarcel
1967130803Smarcel      /* If any of the paths changed in this rev then add or send it. */
1968130803Smarcel      if (changed)
1969130803Smarcel        {
1970130803Smarcel          svn_mergeinfo_t added_mergeinfo = NULL;
1971130803Smarcel          svn_mergeinfo_t deleted_mergeinfo = NULL;
1972130803Smarcel          svn_boolean_t has_children = FALSE;
1973130803Smarcel          apr_hash_t *changes = NULL;
1974130803Smarcel
1975130803Smarcel          /* If we're including merged revisions, we need to calculate
1976130803Smarcel             the mergeinfo deltas committed in this revision to our
1977130803Smarcel             various paths. */
1978130803Smarcel          if (include_merged_revisions)
1979130803Smarcel            {
1980130803Smarcel              apr_array_header_t *cur_paths =
1981130803Smarcel                apr_array_make(iterpool, paths->nelts, sizeof(const char *));
1982130803Smarcel
1983130803Smarcel              /* Get the current paths of our history objects so we can
1984130803Smarcel                 query mergeinfo. */
1985130803Smarcel              /* ### TODO: Should this be ignoring depleted history items? */
1986130803Smarcel              for (i = 0; i < histories->nelts; i++)
1987130803Smarcel                {
1988130803Smarcel                  struct path_info *info = APR_ARRAY_IDX(histories, i,
1989130803Smarcel                                                         struct path_info *);
1990130803Smarcel                  APR_ARRAY_PUSH(cur_paths, const char *) = info->path->data;
1991130803Smarcel                }
1992130803Smarcel              SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo,
1993130803Smarcel                                                     &deleted_mergeinfo,
1994130803Smarcel                                                     &changes,
1995130803Smarcel                                                     fs, cur_paths,
1996130803Smarcel                                                     current, iterpool,
1997130803Smarcel                                                     iterpool));
1998130803Smarcel              has_children = (apr_hash_count(added_mergeinfo) > 0
1999130803Smarcel                              || apr_hash_count(deleted_mergeinfo) > 0);
2000130803Smarcel            }
2001130803Smarcel
2002130803Smarcel          /* If our caller wants logs in descending order, we can send
2003130803Smarcel             'em now (because that's the order we're crawling history
2004130803Smarcel             in anyway). */
2005130803Smarcel          if (descending_order)
2006130803Smarcel            {
2007130803Smarcel              SVN_ERR(send_log(current, fs, changes,
2008130803Smarcel                               log_target_history_as_mergeinfo, nested_merges,
2009130803Smarcel                               discover_changed_paths,
2010130803Smarcel                               subtractive_merge, handling_merged_revisions,
2011130803Smarcel                               revprops, has_children,
2012130803Smarcel                               receiver, receiver_baton,
2013130803Smarcel                               authz_read_func, authz_read_baton, iterpool));
2014130803Smarcel
2015130803Smarcel              if (has_children) /* Implies include_merged_revisions == TRUE */
2016130803Smarcel                {
2017130803Smarcel                  if (!nested_merges)
2018130803Smarcel                    {
2019130803Smarcel                      /* We're at the start of the recursion stack, create a
2020130803Smarcel                         single hash to be shared across all of the merged
2021130803Smarcel                         recursions so we can track and squelch duplicates. */
2022130803Smarcel                      subpool = svn_pool_create(pool);
2023130803Smarcel                      nested_merges = svn_hash__make(subpool);
2024130803Smarcel                      processed = svn_hash__make(subpool);
2025130803Smarcel                    }
2026130803Smarcel
2027130803Smarcel                  SVN_ERR(handle_merged_revisions(
2028130803Smarcel                    current, fs,
2029130803Smarcel                    log_target_history_as_mergeinfo, nested_merges,
2030130803Smarcel                    processed,
2031130803Smarcel                    added_mergeinfo, deleted_mergeinfo,
2032130803Smarcel                    discover_changed_paths,
2033130803Smarcel                    strict_node_history,
2034130803Smarcel                    revprops,
2035130803Smarcel                    receiver, receiver_baton,
2036130803Smarcel                    authz_read_func,
2037130803Smarcel                    authz_read_baton,
2038130803Smarcel                    iterpool));
2039130803Smarcel                }
2040130803Smarcel              if (limit && ++send_count >= limit)
2041130803Smarcel                break;
2042130803Smarcel            }
2043130803Smarcel          /* Otherwise, the caller wanted logs in ascending order, so
2044130803Smarcel             we have to buffer up a list of revs and (if doing
2045130803Smarcel             mergeinfo) a hash of related mergeinfo deltas, and
2046130803Smarcel             process them later. */
2047130803Smarcel          else
2048130803Smarcel            {
2049130803Smarcel              if (! revs)
2050130803Smarcel                revs = apr_array_make(pool, 64, sizeof(svn_revnum_t));
2051130803Smarcel              APR_ARRAY_PUSH(revs, svn_revnum_t) = current;
2052130803Smarcel
2053130803Smarcel              if (added_mergeinfo || deleted_mergeinfo)
2054130803Smarcel                {
2055130803Smarcel                  svn_revnum_t *cur_rev = apr_pcalloc(pool, sizeof(*cur_rev));
2056130803Smarcel                  struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2057130803Smarcel                    apr_palloc(pool, sizeof(*add_and_del_mergeinfo));
2058130803Smarcel
2059130803Smarcel                  if (added_mergeinfo)
2060130803Smarcel                    add_and_del_mergeinfo->added_mergeinfo =
2061130803Smarcel                      svn_mergeinfo_dup(added_mergeinfo, pool);
2062130803Smarcel
2063130803Smarcel                  if (deleted_mergeinfo)
2064130803Smarcel                    add_and_del_mergeinfo->deleted_mergeinfo =
2065130803Smarcel                      svn_mergeinfo_dup(deleted_mergeinfo, pool);
2066130803Smarcel
2067130803Smarcel                  *cur_rev = current;
2068130803Smarcel                  if (! rev_mergeinfo)
2069130803Smarcel                    rev_mergeinfo = svn_hash__make(pool);
2070130803Smarcel                  apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev),
2071130803Smarcel                               add_and_del_mergeinfo);
2072130803Smarcel                }
2073130803Smarcel            }
2074130803Smarcel        }
2075130803Smarcel    }
2076130803Smarcel  svn_pool_destroy(iterpool);
2077130803Smarcel
2078130803Smarcel  if (subpool)
2079130803Smarcel    {
2080130803Smarcel      nested_merges = NULL;
2081130803Smarcel      svn_pool_destroy(subpool);
2082130803Smarcel    }
2083130803Smarcel
2084130803Smarcel  if (revs)
2085130803Smarcel    {
2086130803Smarcel      /* Work loop for processing the revisions we found since they wanted
2087130803Smarcel         history in forward order. */
2088130803Smarcel      iterpool = svn_pool_create(pool);
2089130803Smarcel      for (i = 0; i < revs->nelts; ++i)
2090130803Smarcel        {
2091130803Smarcel          svn_mergeinfo_t added_mergeinfo;
2092130803Smarcel          svn_mergeinfo_t deleted_mergeinfo;
2093130803Smarcel          svn_boolean_t has_children = FALSE;
2094130803Smarcel
2095130803Smarcel          svn_pool_clear(iterpool);
2096130803Smarcel          current = APR_ARRAY_IDX(revs, revs->nelts - i - 1, svn_revnum_t);
2097130803Smarcel
2098130803Smarcel          /* If we've got a hash of revision mergeinfo (which can only
2099130803Smarcel             happen if INCLUDE_MERGED_REVISIONS was set), we check to
2100130803Smarcel             see if this revision is one which merged in other
2101130803Smarcel             revisions we need to handle recursively. */
2102130803Smarcel          if (rev_mergeinfo)
2103130803Smarcel            {
2104130803Smarcel              struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2105130803Smarcel                apr_hash_get(rev_mergeinfo, &current, sizeof(svn_revnum_t));
2106130803Smarcel              added_mergeinfo = add_and_del_mergeinfo->added_mergeinfo;
2107130803Smarcel              deleted_mergeinfo = add_and_del_mergeinfo->deleted_mergeinfo;
2108130803Smarcel              has_children = (apr_hash_count(added_mergeinfo) > 0
2109130803Smarcel                              || apr_hash_count(deleted_mergeinfo) > 0);
2110130803Smarcel            }
2111130803Smarcel
2112130803Smarcel          SVN_ERR(send_log(current, fs, NULL,
2113130803Smarcel                           log_target_history_as_mergeinfo, nested_merges,
2114130803Smarcel                           discover_changed_paths, subtractive_merge,
2115130803Smarcel                           handling_merged_revisions, revprops, has_children,
2116130803Smarcel                           receiver, receiver_baton, authz_read_func,
2117130803Smarcel                           authz_read_baton, iterpool));
2118130803Smarcel          if (has_children)
2119130803Smarcel            {
2120130803Smarcel              if (!nested_merges)
2121130803Smarcel                {
2122130803Smarcel                  subpool = svn_pool_create(pool);
2123130803Smarcel                  nested_merges = svn_hash__make(subpool);
2124130803Smarcel                }
2125130803Smarcel
2126130803Smarcel              SVN_ERR(handle_merged_revisions(current, fs,
2127130803Smarcel                                              log_target_history_as_mergeinfo,
2128130803Smarcel                                              nested_merges,
2129130803Smarcel                                              processed,
2130130803Smarcel                                              added_mergeinfo,
2131130803Smarcel                                              deleted_mergeinfo,
2132130803Smarcel                                              discover_changed_paths,
2133130803Smarcel                                              strict_node_history, revprops,
2134130803Smarcel                                              receiver, receiver_baton,
2135130803Smarcel                                              authz_read_func,
2136130803Smarcel                                              authz_read_baton,
2137130803Smarcel                                              iterpool));
2138130803Smarcel            }
2139130803Smarcel          if (limit && i + 1 >= limit)
2140130803Smarcel            break;
2141130803Smarcel        }
2142130803Smarcel      svn_pool_destroy(iterpool);
2143130803Smarcel    }
2144130803Smarcel
2145130803Smarcel  return SVN_NO_ERROR;
2146130803Smarcel}
2147130803Smarcel
2148130803Smarcelstruct location_segment_baton
2149130803Smarcel{
2150130803Smarcel  apr_array_header_t *history_segments;
2151130803Smarcel  apr_pool_t *pool;
2152130803Smarcel};
2153130803Smarcel
2154130803Smarcel/* svn_location_segment_receiver_t implementation for svn_repos_get_logs4. */
2155130803Smarcelstatic svn_error_t *
2156130803Smarcellocation_segment_receiver(svn_location_segment_t *segment,
2157130803Smarcel                          void *baton,
2158130803Smarcel                          apr_pool_t *pool)
2159130803Smarcel{
2160130803Smarcel  struct location_segment_baton *b = baton;
2161130803Smarcel
2162130803Smarcel  APR_ARRAY_PUSH(b->history_segments, svn_location_segment_t *) =
2163130803Smarcel    svn_location_segment_dup(segment, b->pool);
2164130803Smarcel
2165130803Smarcel  return SVN_NO_ERROR;
2166130803Smarcel}
2167214947Sgonzo
2168214947Sgonzo
2169214947Sgonzo/* Populate *PATHS_HISTORY_MERGEINFO with mergeinfo representing the combined
2170130803Smarcel   history of each path in PATHS between START_REV and END_REV in REPOS's
2171130803Smarcel   filesystem.  START_REV and END_REV must be valid revisions.  RESULT_POOL
2172130803Smarcel   is used to allocate *PATHS_HISTORY_MERGEINFO, SCRATCH_POOL is used for all
2173130803Smarcel   other (temporary) allocations.  Other parameters are the same as
2174130803Smarcel   svn_repos_get_logs4(). */
2175214947Sgonzostatic svn_error_t *
2176214947Sgonzoget_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo,
2177214947Sgonzo                               svn_repos_t *repos,
2178214947Sgonzo                               const apr_array_header_t *paths,
2179130803Smarcel                               svn_revnum_t start_rev,
2180130803Smarcel                               svn_revnum_t end_rev,
2181130803Smarcel                               svn_repos_authz_func_t authz_read_func,
2182130803Smarcel                               void *authz_read_baton,
2183130803Smarcel                               apr_pool_t *result_pool,
2184130803Smarcel                               apr_pool_t *scratch_pool)
2185130803Smarcel{
2186130803Smarcel  int i;
2187130803Smarcel  svn_mergeinfo_t path_history_mergeinfo;
2188130803Smarcel  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2189130803Smarcel
2190130803Smarcel  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_rev));
2191130803Smarcel  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_rev));
2192130803Smarcel
2193130803Smarcel  /* Ensure START_REV is the youngest revision, as required by
2194130803Smarcel     svn_repos_node_location_segments, for which this is an iterative
2195130803Smarcel     wrapper. */
2196130803Smarcel  if (start_rev < end_rev)
2197130803Smarcel    {
2198130803Smarcel      svn_revnum_t tmp_rev = start_rev;
2199130803Smarcel      start_rev = end_rev;
2200130803Smarcel      end_rev = tmp_rev;
2201130803Smarcel    }
2202130803Smarcel
2203130803Smarcel  *paths_history_mergeinfo = svn_hash__make(result_pool);
2204130803Smarcel
2205130803Smarcel  for (i = 0; i < paths->nelts; i++)
2206130803Smarcel    {
2207130803Smarcel      const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
2208130803Smarcel      struct location_segment_baton loc_seg_baton;
2209130803Smarcel
2210130803Smarcel      svn_pool_clear(iterpool);
2211130803Smarcel      loc_seg_baton.pool = scratch_pool;
2212130803Smarcel      loc_seg_baton.history_segments =
2213130803Smarcel        apr_array_make(iterpool, 4, sizeof(svn_location_segment_t *));
2214130803Smarcel
2215130803Smarcel      SVN_ERR(svn_repos_node_location_segments(repos, this_path, start_rev,
2216130803Smarcel                                               start_rev, end_rev,
2217130803Smarcel                                               location_segment_receiver,
2218130803Smarcel                                               &loc_seg_baton,
2219130803Smarcel                                               authz_read_func,
2220130803Smarcel                                               authz_read_baton,
2221130803Smarcel                                               iterpool));
2222130803Smarcel
2223130803Smarcel      SVN_ERR(svn_mergeinfo__mergeinfo_from_segments(
2224130803Smarcel        &path_history_mergeinfo, loc_seg_baton.history_segments, iterpool));
2225130803Smarcel      SVN_ERR(svn_mergeinfo_merge2(*paths_history_mergeinfo,
2226130803Smarcel                                   svn_mergeinfo_dup(path_history_mergeinfo,
2227130803Smarcel                                                     result_pool),
2228130803Smarcel                                   result_pool, iterpool));
2229130803Smarcel    }
2230130803Smarcel  svn_pool_destroy(iterpool);
2231130803Smarcel  return SVN_NO_ERROR;
2232130803Smarcel}
2233130803Smarcel
2234130803Smarcelsvn_error_t *
2235130803Smarcelsvn_repos_get_logs4(svn_repos_t *repos,
2236130803Smarcel                    const apr_array_header_t *paths,
2237130803Smarcel                    svn_revnum_t start,
2238130803Smarcel                    svn_revnum_t end,
2239130803Smarcel                    int limit,
2240130803Smarcel                    svn_boolean_t discover_changed_paths,
2241130803Smarcel                    svn_boolean_t strict_node_history,
2242130803Smarcel                    svn_boolean_t include_merged_revisions,
2243130803Smarcel                    const apr_array_header_t *revprops,
2244130803Smarcel                    svn_repos_authz_func_t authz_read_func,
2245130803Smarcel                    void *authz_read_baton,
2246130803Smarcel                    svn_log_entry_receiver_t receiver,
2247130803Smarcel                    void *receiver_baton,
2248130803Smarcel                    apr_pool_t *pool)
2249130803Smarcel{
2250130803Smarcel  svn_revnum_t head = SVN_INVALID_REVNUM;
2251130803Smarcel  svn_fs_t *fs = repos->fs;
2252130803Smarcel  svn_boolean_t descending_order;
2253130803Smarcel  svn_mergeinfo_t paths_history_mergeinfo = NULL;
2254130803Smarcel
2255130803Smarcel  /* Setup log range. */
2256130803Smarcel  SVN_ERR(svn_fs_youngest_rev(&head, fs, pool));
2257130803Smarcel
2258130803Smarcel  if (! SVN_IS_VALID_REVNUM(start))
2259130803Smarcel    start = head;
2260130803Smarcel
2261130803Smarcel  if (! SVN_IS_VALID_REVNUM(end))
2262130803Smarcel    end = head;
2263130803Smarcel
2264130803Smarcel  /* Check that revisions are sane before ever invoking receiver. */
2265130803Smarcel  if (start > head)
2266130803Smarcel    return svn_error_createf
2267130803Smarcel      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2268130803Smarcel       _("No such revision %ld"), start);
2269130803Smarcel  if (end > head)
2270130803Smarcel    return svn_error_createf
2271130803Smarcel      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2272130803Smarcel       _("No such revision %ld"), end);
2273130803Smarcel
2274130803Smarcel  /* Ensure a youngest-to-oldest revision crawl ordering using our
2275130803Smarcel     (possibly sanitized) range values. */
2276130803Smarcel  descending_order = start >= end;
2277130803Smarcel  if (descending_order)
2278130803Smarcel    {
2279130803Smarcel      svn_revnum_t tmp_rev = start;
2280130803Smarcel      start = end;
2281130803Smarcel      end = tmp_rev;
2282130803Smarcel    }
2283130803Smarcel
2284130803Smarcel  if (! paths)
2285130803Smarcel    paths = apr_array_make(pool, 0, sizeof(const char *));
2286130803Smarcel
2287130803Smarcel  /* If we're not including merged revisions, and we were given no
2288130803Smarcel     paths or a single empty (or "/") path, then we can bypass a bunch
2289130803Smarcel     of complexity because we already know in which revisions the root
2290130803Smarcel     directory was changed -- all of them.  */
2291130803Smarcel  if ((! include_merged_revisions)
2292130803Smarcel      && ((! paths->nelts)
2293130803Smarcel          || ((paths->nelts == 1)
2294130803Smarcel              && (svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *))
2295130803Smarcel                  || (strcmp(APR_ARRAY_IDX(paths, 0, const char *),
2296130803Smarcel                             "/") == 0)))))
2297130803Smarcel    {
2298130803Smarcel      apr_uint64_t send_count = 0;
2299130803Smarcel      int i;
2300130803Smarcel      apr_pool_t *iterpool = svn_pool_create(pool);
2301130803Smarcel
2302130803Smarcel      /* If we are provided an authz callback function, use it to
2303130803Smarcel         verify that the user has read access to the root path in the
2304130803Smarcel         first of our revisions.
2305130803Smarcel
2306130803Smarcel         ### FIXME:  Strictly speaking, we should be checking this
2307130803Smarcel         ### access in every revision along the line.  But currently,
2308130803Smarcel         ### there are no known authz implementations which concern
2309130803Smarcel         ### themselves with per-revision access.  */
2310130803Smarcel      if (authz_read_func)
2311130803Smarcel        {
2312130803Smarcel          svn_boolean_t readable;
2313130803Smarcel          svn_fs_root_t *rev_root;
2314130803Smarcel
2315130803Smarcel          SVN_ERR(svn_fs_revision_root(&rev_root, fs,
2316130803Smarcel                                       descending_order ? end : start, pool));
2317130803Smarcel          SVN_ERR(authz_read_func(&readable, rev_root, "",
2318130803Smarcel                                  authz_read_baton, pool));
2319130803Smarcel          if (! readable)
2320130803Smarcel            return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
2321130803Smarcel        }
2322130803Smarcel
2323130803Smarcel      send_count = end - start + 1;
2324130803Smarcel      if (limit && send_count > limit)
2325130803Smarcel        send_count = limit;
2326130803Smarcel      for (i = 0; i < send_count; ++i)
2327130803Smarcel        {
2328130803Smarcel          svn_revnum_t rev;
2329130803Smarcel
2330130803Smarcel          svn_pool_clear(iterpool);
2331130803Smarcel
2332130803Smarcel          if (descending_order)
2333130803Smarcel            rev = end - i;
2334130803Smarcel          else
2335130803Smarcel            rev = start + i;
2336130803Smarcel          SVN_ERR(send_log(rev, fs, NULL, NULL, NULL,
2337130803Smarcel                           discover_changed_paths, FALSE,
2338130803Smarcel                           FALSE, revprops, FALSE, receiver,
2339130803Smarcel                           receiver_baton, authz_read_func,
2340130803Smarcel                           authz_read_baton, iterpool));
2341130803Smarcel        }
2342130803Smarcel      svn_pool_destroy(iterpool);
2343130803Smarcel
2344130803Smarcel      return SVN_NO_ERROR;
2345130803Smarcel    }
2346130803Smarcel
2347130803Smarcel  /* If we are including merged revisions, then create mergeinfo that
2348130803Smarcel     represents all of PATHS' history between START and END.  We will use
2349130803Smarcel     this later to squelch duplicate log revisions that might exist in
2350130803Smarcel     both natural history and merged-in history.  See
2351130803Smarcel     http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */
2352130803Smarcel  if (include_merged_revisions)
2353130803Smarcel    {
2354130803Smarcel      apr_pool_t *subpool = svn_pool_create(pool);
2355130803Smarcel
2356130803Smarcel      SVN_ERR(get_paths_history_as_mergeinfo(&paths_history_mergeinfo,
2357130803Smarcel                                             repos, paths, start, end,
2358130803Smarcel                                             authz_read_func,
2359130803Smarcel                                             authz_read_baton,
2360130803Smarcel                                             pool, subpool));
2361130803Smarcel      svn_pool_destroy(subpool);
2362130803Smarcel    }
2363130803Smarcel
2364130803Smarcel  return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL, start, end,
2365130803Smarcel                 limit, discover_changed_paths, strict_node_history,
2366130803Smarcel                 include_merged_revisions, FALSE, FALSE, FALSE, revprops,
2367130803Smarcel                 descending_order, receiver, receiver_baton,
2368130803Smarcel                 authz_read_func, authz_read_baton, pool);
2369130803Smarcel}
2370130803Smarcel