log.c revision 251881
1203945Sweongyo/* log.c --- retrieving log messages
2203945Sweongyo *
3203945Sweongyo * ====================================================================
4203945Sweongyo *    Licensed to the Apache Software Foundation (ASF) under one
5203945Sweongyo *    or more contributor license agreements.  See the NOTICE file
6203945Sweongyo *    distributed with this work for additional information
7203945Sweongyo *    regarding copyright ownership.  The ASF licenses this file
8203945Sweongyo *    to you under the Apache License, Version 2.0 (the
9203945Sweongyo *    "License"); you may not use this file except in compliance
10203945Sweongyo *    with the License.  You may obtain a copy of the License at
11203945Sweongyo *
12203945Sweongyo *      http://www.apache.org/licenses/LICENSE-2.0
13203945Sweongyo *
14203945Sweongyo *    Unless required by applicable law or agreed to in writing,
15203945Sweongyo *    software distributed under the License is distributed on an
16203945Sweongyo *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17203945Sweongyo *    KIND, either express or implied.  See the License for the
18203945Sweongyo *    specific language governing permissions and limitations
19203945Sweongyo *    under the License.
20203945Sweongyo * ====================================================================
21203945Sweongyo */
22203945Sweongyo
23203945Sweongyo
24203945Sweongyo#include <stdlib.h>
25203945Sweongyo#define APR_WANT_STRFUNC
26203945Sweongyo#include <apr_want.h>
27203945Sweongyo
28203945Sweongyo#include "svn_compat.h"
29203945Sweongyo#include "svn_private_config.h"
30203945Sweongyo#include "svn_hash.h"
31203945Sweongyo#include "svn_pools.h"
32203945Sweongyo#include "svn_error.h"
33203945Sweongyo#include "svn_path.h"
34203945Sweongyo#include "svn_fs.h"
35203945Sweongyo#include "svn_repos.h"
36203945Sweongyo#include "svn_string.h"
37203945Sweongyo#include "svn_sorts.h"
38203945Sweongyo#include "svn_props.h"
39203945Sweongyo#include "svn_mergeinfo.h"
40203945Sweongyo#include "repos.h"
41203945Sweongyo#include "private/svn_fspath.h"
42203945Sweongyo#include "private/svn_mergeinfo_private.h"
43203945Sweongyo#include "private/svn_subr_private.h"
44203945Sweongyo
45203945Sweongyo
46203945Sweongyo
47203945Sweongyosvn_error_t *
48203945Sweongyosvn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
49203945Sweongyo                                svn_repos_t *repos,
50203945Sweongyo                                svn_revnum_t revision,
51203945Sweongyo                                svn_repos_authz_func_t authz_read_func,
52203945Sweongyo                                void *authz_read_baton,
53203945Sweongyo                                apr_pool_t *pool)
54203945Sweongyo{
55203945Sweongyo  svn_fs_t *fs = svn_repos_fs(repos);
56203945Sweongyo  svn_fs_root_t *rev_root;
57203945Sweongyo  apr_hash_t *changes;
58203945Sweongyo  apr_hash_index_t *hi;
59203945Sweongyo  svn_boolean_t found_readable = FALSE;
60203945Sweongyo  svn_boolean_t found_unreadable = FALSE;
61203945Sweongyo  apr_pool_t *subpool;
62203945Sweongyo
63203945Sweongyo  /* By default, we'll grant full read access to REVISION. */
64203945Sweongyo  *access_level = svn_repos_revision_access_full;
65203945Sweongyo
66203945Sweongyo  /* No auth-checking function?  We're done. */
67203945Sweongyo  if (! authz_read_func)
68204922Sweongyo    return SVN_NO_ERROR;
69204922Sweongyo
70204922Sweongyo  /* Fetch the changes associated with REVISION. */
71204922Sweongyo  SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool));
72204922Sweongyo  SVN_ERR(svn_fs_paths_changed2(&changes, rev_root, pool));
73204922Sweongyo
74203945Sweongyo  /* No changed paths?  We're done. */
75204922Sweongyo  if (apr_hash_count(changes) == 0)
76203945Sweongyo    return SVN_NO_ERROR;
77204922Sweongyo
78203945Sweongyo  /* Otherwise, we have to check the readability of each changed
79203945Sweongyo     path, or at least enough to answer the question asked. */
80203945Sweongyo  subpool = svn_pool_create(pool);
81203945Sweongyo  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
82203945Sweongyo    {
83203945Sweongyo      const void *key;
84203945Sweongyo      void *val;
85203945Sweongyo      svn_fs_path_change2_t *change;
86203945Sweongyo      svn_boolean_t readable;
87203945Sweongyo
88203945Sweongyo      svn_pool_clear(subpool);
89203945Sweongyo      apr_hash_this(hi, &key, NULL, &val);
90203945Sweongyo      change = val;
91203945Sweongyo
92203945Sweongyo      SVN_ERR(authz_read_func(&readable, rev_root, key,
93203945Sweongyo                              authz_read_baton, subpool));
94203945Sweongyo      if (! readable)
95203945Sweongyo        found_unreadable = TRUE;
96203945Sweongyo      else
97203945Sweongyo        found_readable = TRUE;
98203945Sweongyo
99203945Sweongyo      /* If we have at least one of each (readable/unreadable), we
100203945Sweongyo         have our answer. */
101203945Sweongyo      if (found_readable && found_unreadable)
102203945Sweongyo        goto decision;
103203945Sweongyo
104203945Sweongyo      switch (change->change_kind)
105203945Sweongyo        {
106203945Sweongyo        case svn_fs_path_change_add:
107203945Sweongyo        case svn_fs_path_change_replace:
108203945Sweongyo          {
109203945Sweongyo            const char *copyfrom_path;
110203945Sweongyo            svn_revnum_t copyfrom_rev;
111203945Sweongyo
112203945Sweongyo            SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
113203945Sweongyo                                       rev_root, key, subpool));
114203945Sweongyo            if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
115203945Sweongyo              {
116203945Sweongyo                svn_fs_root_t *copyfrom_root;
117203945Sweongyo                SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
118203945Sweongyo                                             copyfrom_rev, subpool));
119203945Sweongyo                SVN_ERR(authz_read_func(&readable,
120203945Sweongyo                                        copyfrom_root, copyfrom_path,
121203945Sweongyo                                        authz_read_baton, subpool));
122203945Sweongyo                if (! readable)
123203945Sweongyo                  found_unreadable = TRUE;
124203945Sweongyo
125203945Sweongyo                /* If we have at least one of each (readable/unreadable), we
126203945Sweongyo                   have our answer. */
127203945Sweongyo                if (found_readable && found_unreadable)
128203945Sweongyo                  goto decision;
129203945Sweongyo              }
130203945Sweongyo          }
131203945Sweongyo          break;
132203945Sweongyo
133203945Sweongyo        case svn_fs_path_change_delete:
134203945Sweongyo        case svn_fs_path_change_modify:
135203945Sweongyo        default:
136203945Sweongyo          break;
137203945Sweongyo        }
138203945Sweongyo    }
139203945Sweongyo
140203945Sweongyo decision:
141203945Sweongyo  svn_pool_destroy(subpool);
142203945Sweongyo
143203945Sweongyo  /* Either every changed path was unreadable... */
144203945Sweongyo  if (! found_readable)
145203945Sweongyo    *access_level = svn_repos_revision_access_none;
146204922Sweongyo
147203945Sweongyo  /* ... or some changed path was unreadable... */
148203945Sweongyo  else if (found_unreadable)
149203945Sweongyo    *access_level = svn_repos_revision_access_partial;
150203945Sweongyo
151203945Sweongyo  /* ... or every changed path was readable (the default). */
152203945Sweongyo  return SVN_NO_ERROR;
153203945Sweongyo}
154203945Sweongyo
155203945Sweongyo
156203945Sweongyo/* Store as keys in CHANGED the paths of all node in ROOT that show a
157203945Sweongyo * significant change.  "Significant" means that the text or
158203945Sweongyo * properties of the node were changed, or that the node was added or
159203945Sweongyo * deleted.
160203945Sweongyo *
161203945Sweongyo * The CHANGED hash set and its keys and values are allocated in POOL;
162203945Sweongyo * keys are const char * paths and values are svn_log_changed_path_t.
163203945Sweongyo *
164203945Sweongyo * To prevent changes from being processed over and over again, the
165203945Sweongyo * changed paths for ROOT may be passed in PREFETCHED_CHANGES.  If the
166203945Sweongyo * latter is NULL, we will request the list inside this function.
167203945Sweongyo *
168203945Sweongyo * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
169203945Sweongyo * AUTHZ_READ_BATON and FS) to check whether each changed-path (and
170203945Sweongyo * copyfrom_path) is readable:
171203945Sweongyo *
172203945Sweongyo *     - If some paths are readable and some are not, then silently
173203945Sweongyo *     omit the unreadable paths from the CHANGED hash, and return
174203945Sweongyo *     SVN_ERR_AUTHZ_PARTIALLY_READABLE.
175203945Sweongyo *
176203945Sweongyo *     - If absolutely every changed-path (and copyfrom_path) is
177203945Sweongyo *     unreadable, then return an empty CHANGED hash and
178203945Sweongyo *     SVN_ERR_AUTHZ_UNREADABLE.  (This is to distinguish a revision
179203945Sweongyo *     which truly has no changed paths from a revision in which all
180203945Sweongyo *     paths are unreadable.)
181203945Sweongyo */
182203945Sweongyostatic svn_error_t *
183203945Sweongyodetect_changed(apr_hash_t **changed,
184203945Sweongyo               svn_fs_root_t *root,
185203945Sweongyo               svn_fs_t *fs,
186203945Sweongyo               apr_hash_t *prefetched_changes,
187203945Sweongyo               svn_repos_authz_func_t authz_read_func,
188203945Sweongyo               void *authz_read_baton,
189203945Sweongyo               apr_pool_t *pool)
190203945Sweongyo{
191203945Sweongyo  apr_hash_t *changes = prefetched_changes;
192203945Sweongyo  apr_hash_index_t *hi;
193203945Sweongyo  apr_pool_t *subpool;
194203945Sweongyo  svn_boolean_t found_readable = FALSE;
195203945Sweongyo  svn_boolean_t found_unreadable = FALSE;
196203945Sweongyo
197203945Sweongyo  *changed = svn_hash__make(pool);
198203945Sweongyo  if (changes == NULL)
199203945Sweongyo    SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
200203945Sweongyo
201203945Sweongyo  if (apr_hash_count(changes) == 0)
202203945Sweongyo    /* No paths changed in this revision?  Uh, sure, I guess the
203203945Sweongyo       revision is readable, then.  */
204203945Sweongyo    return SVN_NO_ERROR;
205203945Sweongyo
206203945Sweongyo  subpool = svn_pool_create(pool);
207203945Sweongyo
208203945Sweongyo  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
209203945Sweongyo    {
210203945Sweongyo      /* NOTE:  Much of this loop is going to look quite similar to
211203945Sweongyo         svn_repos_check_revision_access(), but we have to do more things
212203945Sweongyo         here, so we'll live with the duplication. */
213203945Sweongyo      const void *key;
214203945Sweongyo      void *val;
215203945Sweongyo      svn_fs_path_change2_t *change;
216203945Sweongyo      const char *path;
217203945Sweongyo      char action;
218203945Sweongyo      svn_log_changed_path2_t *item;
219203945Sweongyo
220203945Sweongyo      svn_pool_clear(subpool);
221203945Sweongyo
222203945Sweongyo      /* KEY will be the path, VAL the change. */
223203945Sweongyo      apr_hash_this(hi, &key, NULL, &val);
224203945Sweongyo      path = (const char *) key;
225203945Sweongyo      change = val;
226203945Sweongyo
227203945Sweongyo      /* Skip path if unreadable. */
228203945Sweongyo      if (authz_read_func)
229203945Sweongyo        {
230203945Sweongyo          svn_boolean_t readable;
231203945Sweongyo          SVN_ERR(authz_read_func(&readable,
232203945Sweongyo                                  root, path,
233203945Sweongyo                                  authz_read_baton, subpool));
234203945Sweongyo          if (! readable)
235203945Sweongyo            {
236203945Sweongyo              found_unreadable = TRUE;
237203945Sweongyo              continue;
238203945Sweongyo            }
239203945Sweongyo        }
240203945Sweongyo
241203945Sweongyo      /* At least one changed-path was readable. */
242203945Sweongyo      found_readable = TRUE;
243203945Sweongyo
244203945Sweongyo      switch (change->change_kind)
245203945Sweongyo        {
246203945Sweongyo        case svn_fs_path_change_reset:
247203945Sweongyo          continue;
248203945Sweongyo
249203945Sweongyo        case svn_fs_path_change_add:
250203945Sweongyo          action = 'A';
251203945Sweongyo          break;
252203945Sweongyo
253203945Sweongyo        case svn_fs_path_change_replace:
254203945Sweongyo          action = 'R';
255203945Sweongyo          break;
256203945Sweongyo
257203945Sweongyo        case svn_fs_path_change_delete:
258203945Sweongyo          action = 'D';
259203945Sweongyo          break;
260203945Sweongyo
261203945Sweongyo        case svn_fs_path_change_modify:
262203945Sweongyo        default:
263203945Sweongyo          action = 'M';
264203945Sweongyo          break;
265203945Sweongyo        }
266203945Sweongyo
267203945Sweongyo      item = svn_log_changed_path2_create(pool);
268203945Sweongyo      item->action = action;
269203945Sweongyo      item->node_kind = change->node_kind;
270203945Sweongyo      item->copyfrom_rev = SVN_INVALID_REVNUM;
271203945Sweongyo      item->text_modified = change->text_mod ? svn_tristate_true
272203945Sweongyo                                             : svn_tristate_false;
273203945Sweongyo      item->props_modified = change->prop_mod ? svn_tristate_true
274203945Sweongyo                                              : svn_tristate_false;
275203945Sweongyo
276203945Sweongyo      /* Pre-1.6 revision files don't store the change path kind, so fetch
277203945Sweongyo         it manually. */
278203945Sweongyo      if (item->node_kind == svn_node_unknown)
279203945Sweongyo        {
280203945Sweongyo          svn_fs_root_t *check_root = root;
281203945Sweongyo          const char *check_path = path;
282203945Sweongyo
283203945Sweongyo          /* Deleted items don't exist so check earlier revision.  We
284203945Sweongyo             know the parent must exist and could be a copy */
285203945Sweongyo          if (change->change_kind == svn_fs_path_change_delete)
286203945Sweongyo            {
287203945Sweongyo              svn_fs_history_t *history;
288203945Sweongyo              svn_revnum_t prev_rev;
289203945Sweongyo              const char *parent_path, *name;
290203945Sweongyo
291203945Sweongyo              svn_fspath__split(&parent_path, &name, path, subpool);
292203945Sweongyo
293203945Sweongyo              SVN_ERR(svn_fs_node_history(&history, root, parent_path,
294203945Sweongyo                                          subpool));
295203945Sweongyo
296203945Sweongyo              /* Two calls because the first call returns the original
297203945Sweongyo                 revision as the deleted child means it is 'interesting' */
298203945Sweongyo              SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
299203945Sweongyo              SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
300203945Sweongyo
301203945Sweongyo              SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev, history,
302203945Sweongyo                                              subpool));
303203945Sweongyo              SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev, subpool));
304203945Sweongyo              check_path = svn_fspath__join(parent_path, name, subpool);
305203945Sweongyo            }
306203945Sweongyo
307203945Sweongyo          SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path,
308203945Sweongyo                                    subpool));
309203945Sweongyo        }
310203945Sweongyo
311203945Sweongyo
312203945Sweongyo      if ((action == 'A') || (action == 'R'))
313203945Sweongyo        {
314203945Sweongyo          const char *copyfrom_path = change->copyfrom_path;
315203945Sweongyo          svn_revnum_t copyfrom_rev = change->copyfrom_rev;
316203945Sweongyo
317203945Sweongyo          /* the following is a potentially expensive operation since on FSFS
318203945Sweongyo             we will follow the DAG from ROOT to PATH and that requires
319203945Sweongyo             actually reading the directories along the way. */
320203945Sweongyo          if (!change->copyfrom_known)
321203945Sweongyo            SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
322203945Sweongyo                                      root, path, subpool));
323203945Sweongyo
324203945Sweongyo          if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
325203945Sweongyo            {
326203945Sweongyo              svn_boolean_t readable = TRUE;
327203945Sweongyo
328203945Sweongyo              if (authz_read_func)
329203945Sweongyo                {
330203945Sweongyo                  svn_fs_root_t *copyfrom_root;
331203945Sweongyo
332203945Sweongyo                  SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
333203945Sweongyo                                               copyfrom_rev, subpool));
334203945Sweongyo                  SVN_ERR(authz_read_func(&readable,
335203945Sweongyo                                          copyfrom_root, copyfrom_path,
336203945Sweongyo                                          authz_read_baton, subpool));
337203945Sweongyo                  if (! readable)
338203945Sweongyo                    found_unreadable = TRUE;
339203945Sweongyo                }
340203945Sweongyo
341203945Sweongyo              if (readable)
342203945Sweongyo                {
343203945Sweongyo                  item->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
344203945Sweongyo                  item->copyfrom_rev = copyfrom_rev;
345203945Sweongyo                }
346203945Sweongyo            }
347203945Sweongyo        }
348203945Sweongyo      svn_hash_sets(*changed, apr_pstrdup(pool, path), item);
349203945Sweongyo    }
350203945Sweongyo
351203945Sweongyo  svn_pool_destroy(subpool);
352203945Sweongyo
353203945Sweongyo  if (! found_readable)
354203945Sweongyo    /* Every changed-path was unreadable. */
355203945Sweongyo    return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE,
356203945Sweongyo                            NULL, NULL);
357203945Sweongyo
358203945Sweongyo  if (found_unreadable)
359203945Sweongyo    /* At least one changed-path was unreadable. */
360203945Sweongyo    return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE,
361203945Sweongyo                            NULL, NULL);
362203945Sweongyo
363203945Sweongyo  /* Every changed-path was readable. */
364203945Sweongyo  return SVN_NO_ERROR;
365203945Sweongyo}
366203945Sweongyo
367203945Sweongyo/* This is used by svn_repos_get_logs to keep track of multiple
368203945Sweongyo * path history information while working through history.
369203945Sweongyo *
370203945Sweongyo * The two pools are swapped after each iteration through history because
371203945Sweongyo * to get the next history requires the previous one.
372203945Sweongyo */
373203945Sweongyostruct path_info
374203945Sweongyo{
375203945Sweongyo  svn_stringbuf_t *path;
376203945Sweongyo  svn_revnum_t history_rev;
377203945Sweongyo  svn_boolean_t done;
378203945Sweongyo  svn_boolean_t first_time;
379203945Sweongyo
380203945Sweongyo  /* If possible, we like to keep open the history object for each path,
381203945Sweongyo     since it avoids needed to open and close it many times as we walk
382203945Sweongyo     backwards in time.  To do so we need two pools, so that we can clear
383203945Sweongyo     one each time through.  If we're not holding the history open for
384203945Sweongyo     this path then these three pointers will be NULL. */
385203945Sweongyo  svn_fs_history_t *hist;
386203945Sweongyo  apr_pool_t *newpool;
387203945Sweongyo  apr_pool_t *oldpool;
388203945Sweongyo};
389203945Sweongyo
390203945Sweongyo/* Advance to the next history for the path.
391203945Sweongyo *
392203945Sweongyo * If INFO->HIST is not NULL we do this using that existing history object,
393203945Sweongyo * otherwise we open a new one.
394203945Sweongyo *
395203945Sweongyo * If no more history is available or the history revision is less
396203945Sweongyo * (earlier) than START, or the history is not available due
397203945Sweongyo * to authorization, then INFO->DONE is set to TRUE.
398203945Sweongyo *
399203945Sweongyo * A STRICT value of FALSE will indicate to follow history across copied
400203945Sweongyo * paths.
401203945Sweongyo *
402203945Sweongyo * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
403203945Sweongyo * AUTHZ_READ_BATON and FS) to check whether INFO->PATH is still readable if
404203945Sweongyo * we do indeed find more history for the path.
405203945Sweongyo */
406203945Sweongyostatic svn_error_t *
407203945Sweongyoget_history(struct path_info *info,
408203945Sweongyo            svn_fs_t *fs,
409203945Sweongyo            svn_boolean_t strict,
410203945Sweongyo            svn_repos_authz_func_t authz_read_func,
411203945Sweongyo            void *authz_read_baton,
412203945Sweongyo            svn_revnum_t start,
413203945Sweongyo            apr_pool_t *pool)
414203945Sweongyo{
415203945Sweongyo  svn_fs_root_t *history_root = NULL;
416203945Sweongyo  svn_fs_history_t *hist;
417203945Sweongyo  apr_pool_t *subpool;
418203945Sweongyo  const char *path;
419203945Sweongyo
420203945Sweongyo  if (info->hist)
421203945Sweongyo    {
422203945Sweongyo      subpool = info->newpool;
423203945Sweongyo
424203945Sweongyo      SVN_ERR(svn_fs_history_prev(&info->hist, info->hist, ! strict, subpool));
425203945Sweongyo
426203945Sweongyo      hist = info->hist;
427203945Sweongyo    }
428203945Sweongyo  else
429203945Sweongyo    {
430203945Sweongyo      subpool = svn_pool_create(pool);
431203945Sweongyo
432203945Sweongyo      /* Open the history located at the last rev we were at. */
433203945Sweongyo      SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev,
434203945Sweongyo                                   subpool));
435203945Sweongyo
436203945Sweongyo      SVN_ERR(svn_fs_node_history(&hist, history_root, info->path->data,
437203945Sweongyo                                  subpool));
438203945Sweongyo
439203945Sweongyo      SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
440203945Sweongyo
441203945Sweongyo      if (info->first_time)
442203945Sweongyo        info->first_time = FALSE;
443203945Sweongyo      else
444203945Sweongyo        SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
445203945Sweongyo    }
446203945Sweongyo
447203945Sweongyo  if (! hist)
448203945Sweongyo    {
449203945Sweongyo      svn_pool_destroy(subpool);
450203945Sweongyo      if (info->oldpool)
451203945Sweongyo        svn_pool_destroy(info->oldpool);
452203945Sweongyo      info->done = TRUE;
453203945Sweongyo      return SVN_NO_ERROR;
454203945Sweongyo    }
455203945Sweongyo
456203945Sweongyo  /* Fetch the location information for this history step. */
457203945Sweongyo  SVN_ERR(svn_fs_history_location(&path, &info->history_rev,
458203945Sweongyo                                  hist, subpool));
459203945Sweongyo
460203945Sweongyo  svn_stringbuf_set(info->path, path);
461203945Sweongyo
462203945Sweongyo  /* If this history item predates our START revision then
463203945Sweongyo     don't fetch any more for this path. */
464203945Sweongyo  if (info->history_rev < start)
465203945Sweongyo    {
466203945Sweongyo      svn_pool_destroy(subpool);
467203945Sweongyo      if (info->oldpool)
468203945Sweongyo        svn_pool_destroy(info->oldpool);
469203945Sweongyo      info->done = TRUE;
470203945Sweongyo      return SVN_NO_ERROR;
471203945Sweongyo    }
472203945Sweongyo
473203945Sweongyo  /* Is the history item readable?  If not, done with path. */
474203945Sweongyo  if (authz_read_func)
475203945Sweongyo    {
476203945Sweongyo      svn_boolean_t readable;
477203945Sweongyo      SVN_ERR(svn_fs_revision_root(&history_root, fs,
478203945Sweongyo                                   info->history_rev,
479203945Sweongyo                                   subpool));
480203945Sweongyo      SVN_ERR(authz_read_func(&readable, history_root,
481203945Sweongyo                              info->path->data,
482203945Sweongyo                              authz_read_baton,
483203945Sweongyo                              subpool));
484203945Sweongyo      if (! readable)
485203945Sweongyo        info->done = TRUE;
486203945Sweongyo    }
487203945Sweongyo
488203945Sweongyo  if (! info->hist)
489203945Sweongyo    {
490203945Sweongyo      svn_pool_destroy(subpool);
491203945Sweongyo    }
492203945Sweongyo  else
493203945Sweongyo    {
494203945Sweongyo      apr_pool_t *temppool = info->oldpool;
495203945Sweongyo      info->oldpool = info->newpool;
496203945Sweongyo      svn_pool_clear(temppool);
497203945Sweongyo      info->newpool = temppool;
498203945Sweongyo    }
499203945Sweongyo
500203945Sweongyo  return SVN_NO_ERROR;
501203945Sweongyo}
502203945Sweongyo
503203945Sweongyo/* Set INFO->HIST to the next history for the path *if* there is history
504203945Sweongyo * available and INFO->HISTORY_REV is equal to or greater than CURRENT.
505203945Sweongyo *
506203945Sweongyo * *CHANGED is set to TRUE if the path has history in the CURRENT revision,
507203945Sweongyo * otherwise it is not touched.
508203945Sweongyo *
509203945Sweongyo * If we do need to get the next history revision for the path, call
510203945Sweongyo * get_history to do it -- see it for details.
511203945Sweongyo */
512203945Sweongyostatic svn_error_t *
513203945Sweongyocheck_history(svn_boolean_t *changed,
514203945Sweongyo              struct path_info *info,
515203945Sweongyo              svn_fs_t *fs,
516203945Sweongyo              svn_revnum_t current,
517203945Sweongyo              svn_boolean_t strict,
518203945Sweongyo              svn_repos_authz_func_t authz_read_func,
519203945Sweongyo              void *authz_read_baton,
520204257Sweongyo              svn_revnum_t start,
521204257Sweongyo              apr_pool_t *pool)
522203945Sweongyo{
523203945Sweongyo  /* If we're already done with histories for this path,
524203945Sweongyo     don't try to fetch any more. */
525203945Sweongyo  if (info->done)
526203945Sweongyo    return SVN_NO_ERROR;
527203945Sweongyo
528203945Sweongyo  /* If the last rev we got for this path is less than CURRENT,
529203945Sweongyo     then just return and don't fetch history for this path.
530203945Sweongyo     The caller will get to this rev eventually or else reach
531203945Sweongyo     the limit. */
532203945Sweongyo  if (info->history_rev < current)
533203945Sweongyo    return SVN_NO_ERROR;
534203945Sweongyo
535203945Sweongyo  /* If the last rev we got for this path is equal to CURRENT
536203945Sweongyo     then set *CHANGED to true and get the next history
537203945Sweongyo     rev where this path was changed. */
538203945Sweongyo  *changed = TRUE;
539203945Sweongyo  return get_history(info, fs, strict, authz_read_func,
540203945Sweongyo                     authz_read_baton, start, pool);
541203945Sweongyo}
542203945Sweongyo
543203945Sweongyo/* Return the next interesting revision in our list of HISTORIES. */
544203945Sweongyostatic svn_revnum_t
545203945Sweongyonext_history_rev(const apr_array_header_t *histories)
546203945Sweongyo{
547203945Sweongyo  svn_revnum_t next_rev = SVN_INVALID_REVNUM;
548203945Sweongyo  int i;
549203945Sweongyo
550203945Sweongyo  for (i = 0; i < histories->nelts; ++i)
551203945Sweongyo    {
552203945Sweongyo      struct path_info *info = APR_ARRAY_IDX(histories, i,
553203945Sweongyo                                             struct path_info *);
554203945Sweongyo      if (info->done)
555203945Sweongyo        continue;
556203945Sweongyo      if (info->history_rev > next_rev)
557203945Sweongyo        next_rev = info->history_rev;
558203945Sweongyo    }
559203945Sweongyo
560203945Sweongyo  return next_rev;
561203945Sweongyo}
562203945Sweongyo
563203945Sweongyo/* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to
564203945Sweongyo   catalogs describing how mergeinfo values on paths (which are the
565203945Sweongyo   keys of those catalogs) were changed in REV.  If *PREFETCHED_CAHNGES
566203945Sweongyo   already contains the changed paths for REV, use that.  Otherwise,
567203945Sweongyo   request that data and return it in *PREFETCHED_CHANGES. */
568203945Sweongyo/* ### TODO: This would make a *great*, useful public function,
569203945Sweongyo   ### svn_repos_fs_mergeinfo_changed()!  -- cmpilato  */
570203945Sweongyostatic svn_error_t *
571203945Sweongyofs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
572203945Sweongyo                     svn_mergeinfo_catalog_t *added_mergeinfo_catalog,
573203945Sweongyo                     apr_hash_t **prefetched_changes,
574203945Sweongyo                     svn_fs_t *fs,
575203945Sweongyo                     svn_revnum_t rev,
576203945Sweongyo                     apr_pool_t *result_pool,
577203945Sweongyo                     apr_pool_t *scratch_pool)
578203945Sweongyo
579203945Sweongyo{
580203945Sweongyo  svn_fs_root_t *root;
581203945Sweongyo  apr_pool_t *iterpool;
582203945Sweongyo  apr_hash_index_t *hi;
583203945Sweongyo
584203945Sweongyo  /* Initialize return variables. */
585203945Sweongyo  *deleted_mergeinfo_catalog = svn_hash__make(result_pool);
586203945Sweongyo  *added_mergeinfo_catalog = svn_hash__make(result_pool);
587203945Sweongyo
588203945Sweongyo  /* Revision 0 has no mergeinfo and no mergeinfo changes. */
589203945Sweongyo  if (rev == 0)
590203945Sweongyo    return SVN_NO_ERROR;
591203945Sweongyo
592203945Sweongyo  /* We're going to use the changed-paths information for REV to
593203945Sweongyo     narrow down our search. */
594203945Sweongyo  SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
595203945Sweongyo  if (*prefetched_changes == NULL)
596203945Sweongyo    SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool));
597203945Sweongyo
598203945Sweongyo  /* No changed paths?  We're done. */
599203945Sweongyo  if (apr_hash_count(*prefetched_changes) == 0)
600203945Sweongyo    return SVN_NO_ERROR;
601203945Sweongyo
602203945Sweongyo  /* Loop over changes, looking for anything that might carry an
603203945Sweongyo     svn:mergeinfo change and is one of our paths of interest, or a
604203945Sweongyo     child or [grand]parent directory thereof. */
605203945Sweongyo  iterpool = svn_pool_create(scratch_pool);
606203945Sweongyo  for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
607203945Sweongyo       hi;
608203945Sweongyo       hi = apr_hash_next(hi))
609203945Sweongyo    {
610203945Sweongyo      const void *key;
611203945Sweongyo      void *val;
612203945Sweongyo      svn_fs_path_change2_t *change;
613203945Sweongyo      const char *changed_path, *base_path = NULL;
614203945Sweongyo      svn_revnum_t base_rev = SVN_INVALID_REVNUM;
615203945Sweongyo      svn_fs_root_t *base_root = NULL;
616203945Sweongyo      svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value;
617203945Sweongyo
618203945Sweongyo      svn_pool_clear(iterpool);
619203945Sweongyo
620203945Sweongyo      /* KEY will be the path, VAL the change. */
621203945Sweongyo      apr_hash_this(hi, &key, NULL, &val);
622203945Sweongyo      changed_path = key;
623203945Sweongyo      change = val;
624203945Sweongyo
625203945Sweongyo      /* If there was no property change on this item, ignore it. */
626203945Sweongyo      if (! change->prop_mod)
627203945Sweongyo        continue;
628203945Sweongyo
629203945Sweongyo      switch (change->change_kind)
630203945Sweongyo        {
631203945Sweongyo
632203945Sweongyo        /* ### TODO: Can the add, replace, and modify cases be joined
633203945Sweongyo           ### together to all use svn_repos__prev_location()?  The
634203945Sweongyo           ### difference would be the fallback case (path/rev-1 for
635203945Sweongyo           ### modifies, NULL otherwise).  -- cmpilato  */
636203945Sweongyo
637203945Sweongyo        /* If the path was added or replaced, see if it was created via
638203945Sweongyo           copy.  If so, that will tell us where its previous location
639203945Sweongyo           was.  If not, there's no previous location to examine.  */
640203945Sweongyo        case svn_fs_path_change_add:
641203945Sweongyo        case svn_fs_path_change_replace:
642203945Sweongyo          {
643203945Sweongyo            const char *copyfrom_path;
644203945Sweongyo            svn_revnum_t copyfrom_rev;
645203945Sweongyo
646203945Sweongyo            SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
647203945Sweongyo                                       root, changed_path, iterpool));
648203945Sweongyo            if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
649203945Sweongyo              {
650203945Sweongyo                base_path = apr_pstrdup(scratch_pool, copyfrom_path);
651203945Sweongyo                base_rev = copyfrom_rev;
652203945Sweongyo              }
653203945Sweongyo            break;
654203945Sweongyo          }
655203945Sweongyo
656203945Sweongyo        /* If the path was merely modified, see if its previous
657203945Sweongyo           location was affected by a copy which happened in this
658203945Sweongyo           revision before assuming it holds the same path it did the
659203945Sweongyo           previous revision. */
660203945Sweongyo        case svn_fs_path_change_modify:
661203945Sweongyo          {
662203945Sweongyo            svn_revnum_t appeared_rev;
663203945Sweongyo
664203945Sweongyo            SVN_ERR(svn_repos__prev_location(&appeared_rev, &base_path,
665203945Sweongyo                                             &base_rev, fs, rev,
666203945Sweongyo                                             changed_path, iterpool));
667203945Sweongyo
668203945Sweongyo            /* If this path isn't the result of a copy that occurred
669203945Sweongyo               in this revision, we can find the previous version of
670203945Sweongyo               it in REV - 1 at the same path. */
671203945Sweongyo            if (! (base_path && SVN_IS_VALID_REVNUM(base_rev)
672203945Sweongyo                   && (appeared_rev == rev)))
673203945Sweongyo              {
674203945Sweongyo                base_path = changed_path;
675203945Sweongyo                base_rev = rev - 1;
676203945Sweongyo              }
677203945Sweongyo            break;
678203945Sweongyo          }
679203945Sweongyo
680203945Sweongyo        /* We don't care about any of the other cases. */
681203945Sweongyo        case svn_fs_path_change_delete:
682203945Sweongyo        case svn_fs_path_change_reset:
683203945Sweongyo        default:
684203945Sweongyo          continue;
685203945Sweongyo        }
686203945Sweongyo
687203945Sweongyo      /* If there was a base location, fetch its mergeinfo property value. */
688203945Sweongyo      if (base_path && SVN_IS_VALID_REVNUM(base_rev))
689203945Sweongyo        {
690203945Sweongyo          SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, iterpool));
691203945Sweongyo          SVN_ERR(svn_fs_node_prop(&prev_mergeinfo_value, base_root, base_path,
692203945Sweongyo                                   SVN_PROP_MERGEINFO, iterpool));
693203945Sweongyo        }
694203945Sweongyo
695203945Sweongyo      /* Now fetch the current (as of REV) mergeinfo property value. */
696203945Sweongyo      SVN_ERR(svn_fs_node_prop(&mergeinfo_value, root, changed_path,
697203945Sweongyo                               SVN_PROP_MERGEINFO, iterpool));
698203945Sweongyo
699203945Sweongyo      /* No mergeinfo on either the new or previous location?  Just
700203945Sweongyo         skip it.  (If there *was* a change, it would have been in
701203945Sweongyo         inherited mergeinfo only, which should be picked up by the
702203945Sweongyo         iteration of this loop that finds the parent paths that
703203945Sweongyo         really got changed.)  */
704203945Sweongyo      if (! (mergeinfo_value || prev_mergeinfo_value))
705203945Sweongyo        continue;
706203945Sweongyo
707203945Sweongyo      /* If mergeinfo was explicitly added or removed on this path, we
708203945Sweongyo         need to check to see if that was a real semantic change of
709203945Sweongyo         meaning.  So, fill in the "missing" mergeinfo value with the
710203945Sweongyo         inherited mergeinfo for that path/revision.  */
711203945Sweongyo      if (prev_mergeinfo_value && (! mergeinfo_value))
712203945Sweongyo        {
713203945Sweongyo          apr_array_header_t *query_paths =
714203945Sweongyo            apr_array_make(iterpool, 1, sizeof(const char *));
715203945Sweongyo          svn_mergeinfo_t tmp_mergeinfo;
716203945Sweongyo          svn_mergeinfo_catalog_t tmp_catalog;
717228399Seadler
718203945Sweongyo          APR_ARRAY_PUSH(query_paths, const char *) = changed_path;
719203945Sweongyo          SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root,
720203945Sweongyo                                        query_paths, svn_mergeinfo_inherited,
721203945Sweongyo                                        FALSE, TRUE, iterpool, iterpool));
722203945Sweongyo          tmp_mergeinfo = svn_hash_gets(tmp_catalog, changed_path);
723203945Sweongyo          if (tmp_mergeinfo)
724203945Sweongyo            SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_value,
725203945Sweongyo                                            tmp_mergeinfo,
726203945Sweongyo                                            iterpool));
727203945Sweongyo        }
728203945Sweongyo      else if (mergeinfo_value && (! prev_mergeinfo_value)
729203945Sweongyo               && base_path && SVN_IS_VALID_REVNUM(base_rev))
730203945Sweongyo        {
731203945Sweongyo          apr_array_header_t *query_paths =
732203945Sweongyo            apr_array_make(iterpool, 1, sizeof(const char *));
733203945Sweongyo          svn_mergeinfo_t tmp_mergeinfo;
734203945Sweongyo          svn_mergeinfo_catalog_t tmp_catalog;
735203945Sweongyo
736203945Sweongyo          APR_ARRAY_PUSH(query_paths, const char *) = base_path;
737203945Sweongyo          SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, base_root,
738203945Sweongyo                                        query_paths, svn_mergeinfo_inherited,
739203945Sweongyo                                        FALSE, TRUE, iterpool, iterpool));
740203945Sweongyo          tmp_mergeinfo = svn_hash_gets(tmp_catalog, base_path);
741203945Sweongyo          if (tmp_mergeinfo)
742203945Sweongyo            SVN_ERR(svn_mergeinfo_to_string(&prev_mergeinfo_value,
743203945Sweongyo                                            tmp_mergeinfo,
744203945Sweongyo                                            iterpool));
745203945Sweongyo        }
746203945Sweongyo
747203945Sweongyo      /* If the old and new mergeinfo differ in any way, store the
748203945Sweongyo         before and after mergeinfo values in our return hashes. */
749203945Sweongyo      if ((prev_mergeinfo_value && (! mergeinfo_value))
750203945Sweongyo          || ((! prev_mergeinfo_value) && mergeinfo_value)
751203945Sweongyo          || (prev_mergeinfo_value && mergeinfo_value
752203945Sweongyo              && (! svn_string_compare(mergeinfo_value,
753203945Sweongyo                                       prev_mergeinfo_value))))
754203945Sweongyo        {
755203945Sweongyo          svn_mergeinfo_t prev_mergeinfo = NULL, mergeinfo = NULL;
756203945Sweongyo          svn_mergeinfo_t deleted, added;
757203945Sweongyo          const char *hash_path;
758203945Sweongyo
759203945Sweongyo          if (mergeinfo_value)
760203945Sweongyo            SVN_ERR(svn_mergeinfo_parse(&mergeinfo,
761203945Sweongyo                                        mergeinfo_value->data, iterpool));
762203945Sweongyo          if (prev_mergeinfo_value)
763203945Sweongyo            SVN_ERR(svn_mergeinfo_parse(&prev_mergeinfo,
764203945Sweongyo                                        prev_mergeinfo_value->data, iterpool));
765203945Sweongyo          SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
766203945Sweongyo                                      mergeinfo, FALSE, result_pool,
767203945Sweongyo                                      iterpool));
768203945Sweongyo
769203945Sweongyo          /* Toss interesting stuff into our return catalogs. */
770203945Sweongyo          hash_path = apr_pstrdup(result_pool, changed_path);
771203945Sweongyo          svn_hash_sets(*deleted_mergeinfo_catalog, hash_path, deleted);
772203945Sweongyo          svn_hash_sets(*added_mergeinfo_catalog, hash_path, added);
773203945Sweongyo        }
774203945Sweongyo    }
775203945Sweongyo
776203945Sweongyo  svn_pool_destroy(iterpool);
777203945Sweongyo  return SVN_NO_ERROR;
778203945Sweongyo}
779203945Sweongyo
780203945Sweongyo
781203945Sweongyo/* Determine what (if any) mergeinfo for PATHS was modified in
782203945Sweongyo   revision REV, returning the differences for added mergeinfo in
783203945Sweongyo   *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO.
784203945Sweongyo   If *PREFETCHED_CAHNGES already contains the changed paths for
785203945Sweongyo   REV, use that.  Otherwise, request that data and return it in
786203945Sweongyo   *PREFETCHED_CHANGES.
787203945Sweongyo   Use POOL for all allocations. */
788203945Sweongyostatic svn_error_t *
789203945Sweongyoget_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
790203945Sweongyo                               svn_mergeinfo_t *deleted_mergeinfo,
791203945Sweongyo                               apr_hash_t **prefetched_changes,
792203945Sweongyo                               svn_fs_t *fs,
793203945Sweongyo                               const apr_array_header_t *paths,
794203945Sweongyo                               svn_revnum_t rev,
795203945Sweongyo                               apr_pool_t *result_pool,
796203945Sweongyo                               apr_pool_t *scratch_pool)
797203945Sweongyo{
798203945Sweongyo  svn_mergeinfo_catalog_t added_mergeinfo_catalog, deleted_mergeinfo_catalog;
799203945Sweongyo  apr_hash_index_t *hi;
800203945Sweongyo  svn_fs_root_t *root;
801203945Sweongyo  apr_pool_t *iterpool;
802203945Sweongyo  int i;
803203945Sweongyo  svn_error_t *err;
804203945Sweongyo
805203945Sweongyo  /* Initialize return value. */
806203945Sweongyo  *added_mergeinfo = svn_hash__make(result_pool);
807203945Sweongyo  *deleted_mergeinfo = svn_hash__make(result_pool);
808203945Sweongyo
809203945Sweongyo  /* If we're asking about revision 0, there's no mergeinfo to be found. */
810203945Sweongyo  if (rev == 0)
811203945Sweongyo    return SVN_NO_ERROR;
812203945Sweongyo
813203945Sweongyo  /* No paths?  No mergeinfo. */
814203945Sweongyo  if (! paths->nelts)
815203945Sweongyo    return SVN_NO_ERROR;
816203945Sweongyo
817203945Sweongyo  /* Create a work subpool and get a root for REV. */
818203945Sweongyo  SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
819203945Sweongyo
820203945Sweongyo  /* Fetch the mergeinfo changes for REV. */
821203945Sweongyo  err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog,
822203945Sweongyo                             &added_mergeinfo_catalog,
823203945Sweongyo                             prefetched_changes,
824203945Sweongyo                             fs, rev, scratch_pool, scratch_pool);
825203945Sweongyo  if (err)
826203945Sweongyo    {
827203945Sweongyo      if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
828203945Sweongyo        {
829203945Sweongyo          /* Issue #3896: If invalid mergeinfo is encountered the
830203945Sweongyo             best we can do is ignore it and act as if there were
831203945Sweongyo             no mergeinfo modifications. */
832203945Sweongyo          svn_error_clear(err);
833203945Sweongyo          return SVN_NO_ERROR;
834203945Sweongyo        }
835203945Sweongyo      else
836203945Sweongyo        {
837203945Sweongyo          return svn_error_trace(err);
838203945Sweongyo        }
839203945Sweongyo    }
840203945Sweongyo
841203945Sweongyo  /* In most revisions, there will be no mergeinfo change at all. */
842203945Sweongyo  if (   apr_hash_count(deleted_mergeinfo_catalog) == 0
843203945Sweongyo      && apr_hash_count(added_mergeinfo_catalog) == 0)
844203945Sweongyo    return SVN_NO_ERROR;
845203945Sweongyo
846203945Sweongyo  /* Check our PATHS for any changes to their inherited mergeinfo.
847203945Sweongyo     (We deal with changes to mergeinfo directly *on* the paths in the
848203945Sweongyo     following loop.)  */
849203945Sweongyo  iterpool = svn_pool_create(scratch_pool);
850203945Sweongyo  for (i = 0; i < paths->nelts; i++)
851203945Sweongyo    {
852203945Sweongyo      const char *path = APR_ARRAY_IDX(paths, i, const char *);
853203945Sweongyo      const char *prev_path;
854203945Sweongyo      apr_ssize_t klen;
855203945Sweongyo      svn_revnum_t appeared_rev, prev_rev;
856203945Sweongyo      svn_fs_root_t *prev_root;
857203945Sweongyo      svn_mergeinfo_catalog_t catalog, inherited_catalog;
858203945Sweongyo      svn_mergeinfo_t prev_mergeinfo, mergeinfo, deleted, added,
859203945Sweongyo        prev_inherited_mergeinfo, inherited_mergeinfo;
860203945Sweongyo      apr_array_header_t *query_paths;
861203945Sweongyo
862203945Sweongyo      svn_pool_clear(iterpool);
863203945Sweongyo
864203945Sweongyo      /* If this path is represented in the changed-mergeinfo hashes,
865203945Sweongyo         we'll deal with it in the loop below. */
866203945Sweongyo      if (svn_hash_gets(deleted_mergeinfo_catalog, path))
867203945Sweongyo        continue;
868203945Sweongyo
869203945Sweongyo      /* Figure out what path/rev to compare against.  Ignore
870203945Sweongyo         not-found errors returned by the filesystem.  */
871203945Sweongyo      err = svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
872203945Sweongyo                                     fs, rev, path, iterpool);
873203945Sweongyo      if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
874203945Sweongyo                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))
875203945Sweongyo        {
876203945Sweongyo          svn_error_clear(err);
877203945Sweongyo          err = SVN_NO_ERROR;
878203945Sweongyo          continue;
879203945Sweongyo        }
880203945Sweongyo      SVN_ERR(err);
881203945Sweongyo
882203945Sweongyo      /* If this path isn't the result of a copy that occurred in this
883203945Sweongyo         revision, we can find the previous version of it in REV - 1
884203945Sweongyo         at the same path. */
885203945Sweongyo      if (! (prev_path && SVN_IS_VALID_REVNUM(prev_rev)
886203945Sweongyo             && (appeared_rev == rev)))
887203945Sweongyo        {
888203945Sweongyo          prev_path = path;
889203945Sweongyo          prev_rev = rev - 1;
890203945Sweongyo        }
891203945Sweongyo
892203945Sweongyo      /* Fetch the previous mergeinfo (including inherited stuff) for
893203945Sweongyo         this path.  Ignore not-found errors returned by the
894203945Sweongyo         filesystem or invalid mergeinfo (Issue #3896).*/
895203945Sweongyo      SVN_ERR(svn_fs_revision_root(&prev_root, fs, prev_rev, iterpool));
896203945Sweongyo      query_paths = apr_array_make(iterpool, 1, sizeof(const char *));
897203945Sweongyo      APR_ARRAY_PUSH(query_paths, const char *) = prev_path;
898203945Sweongyo      err = svn_fs_get_mergeinfo2(&catalog, prev_root, query_paths,
899203945Sweongyo                                  svn_mergeinfo_inherited, FALSE, TRUE,
900203945Sweongyo                                  iterpool, iterpool);
901203945Sweongyo      if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
902203945Sweongyo                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
903203945Sweongyo                  err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
904203945Sweongyo        {
905203945Sweongyo          svn_error_clear(err);
906203945Sweongyo          err = SVN_NO_ERROR;
907203945Sweongyo          continue;
908203945Sweongyo        }
909203945Sweongyo      SVN_ERR(err);
910203945Sweongyo
911203945Sweongyo      /* Issue #4022 'svn log -g interprets change in inherited mergeinfo due
912203945Sweongyo         to move as a merge': A copy where the source and destination inherit
913203945Sweongyo         mergeinfo from the same parent means the inherited mergeinfo of the
914203945Sweongyo         source and destination will differ, but this diffrence is not
915203945Sweongyo         indicative of a merge unless the mergeinfo on the inherited parent
916203945Sweongyo         has actually changed.
917203945Sweongyo
918203945Sweongyo         To check for this we must fetch the "raw" previous inherited
919203945Sweongyo         mergeinfo and the "raw" mergeinfo @REV then compare these. */
920203945Sweongyo      SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, prev_root, query_paths,
921203945Sweongyo                                    svn_mergeinfo_nearest_ancestor, FALSE,
922203945Sweongyo                                    FALSE, /* adjust_inherited_mergeinfo */
923203945Sweongyo                                    iterpool, iterpool));
924203945Sweongyo
925203945Sweongyo      klen = strlen(prev_path);
926203945Sweongyo      prev_mergeinfo = apr_hash_get(catalog, prev_path, klen);
927203945Sweongyo      prev_inherited_mergeinfo = apr_hash_get(inherited_catalog, prev_path, klen);
928203945Sweongyo
929203945Sweongyo      /* Fetch the current mergeinfo (as of REV, and including
930203945Sweongyo         inherited stuff) for this path. */
931203945Sweongyo      APR_ARRAY_IDX(query_paths, 0, const char *) = path;
932203945Sweongyo      SVN_ERR(svn_fs_get_mergeinfo2(&catalog, root, query_paths,
933203945Sweongyo                                    svn_mergeinfo_inherited, FALSE, TRUE,
934203945Sweongyo                                    iterpool, iterpool));
935203945Sweongyo
936203945Sweongyo      /* Issue #4022 again, fetch the raw inherited mergeinfo. */
937203945Sweongyo      SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, root, query_paths,
938203945Sweongyo                                    svn_mergeinfo_nearest_ancestor, FALSE,
939203945Sweongyo                                    FALSE, /* adjust_inherited_mergeinfo */
940203945Sweongyo                                    iterpool, iterpool));
941203945Sweongyo
942203945Sweongyo      klen = strlen(path);
943203945Sweongyo      mergeinfo = apr_hash_get(catalog, path, klen);
944203945Sweongyo      inherited_mergeinfo = apr_hash_get(inherited_catalog, path, klen);
945203945Sweongyo
946203945Sweongyo      if (!prev_mergeinfo && !mergeinfo)
947203945Sweongyo        continue;
948203945Sweongyo
949203945Sweongyo      /* Last bit of issue #4022 checking. */
950203945Sweongyo      if (prev_inherited_mergeinfo && inherited_mergeinfo)
951203945Sweongyo        {
952203945Sweongyo          svn_boolean_t inherits_same_mergeinfo;
953
954          SVN_ERR(svn_mergeinfo__equals(&inherits_same_mergeinfo,
955                                        prev_inherited_mergeinfo,
956                                        inherited_mergeinfo,
957                                        TRUE, iterpool));
958          /* If a copy rather than an actual merge brought about an
959             inherited mergeinfo change then we are finished. */
960          if (inherits_same_mergeinfo)
961            continue;
962        }
963      else
964        {
965          svn_boolean_t same_mergeinfo;
966          SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo,
967                                        prev_inherited_mergeinfo,
968                                        FALSE,
969                                        TRUE, iterpool));
970          if (same_mergeinfo)
971            continue;
972        }
973
974      /* Compare, constrast, and combine the results. */
975      SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
976                                  mergeinfo, FALSE, result_pool, iterpool));
977      SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, deleted,
978                                   result_pool, iterpool));
979      SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, added,
980                                   result_pool, iterpool));
981     }
982
983  /* Merge all the mergeinfos which are, or are children of, one of
984     our paths of interest into one giant delta mergeinfo.  */
985  for (hi = apr_hash_first(scratch_pool, added_mergeinfo_catalog);
986       hi; hi = apr_hash_next(hi))
987    {
988      const void *key;
989      apr_ssize_t klen;
990      void *val;
991      const char *changed_path;
992      svn_mergeinfo_t added, deleted;
993
994      /* The path is the key, the mergeinfo delta is the value. */
995      apr_hash_this(hi, &key, &klen, &val);
996      changed_path = key;
997      added = val;
998
999      for (i = 0; i < paths->nelts; i++)
1000        {
1001          const char *path = APR_ARRAY_IDX(paths, i, const char *);
1002          if (! svn_fspath__skip_ancestor(path, changed_path))
1003            continue;
1004          svn_pool_clear(iterpool);
1005          deleted = apr_hash_get(deleted_mergeinfo_catalog, key, klen);
1006          SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo,
1007                                       svn_mergeinfo_dup(deleted, result_pool),
1008                                       result_pool, iterpool));
1009          SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo,
1010                                       svn_mergeinfo_dup(added, result_pool),
1011                                       result_pool, iterpool));
1012
1013          break;
1014        }
1015    }
1016
1017  svn_pool_destroy(iterpool);
1018  return SVN_NO_ERROR;
1019}
1020
1021
1022/* Fill LOG_ENTRY with history information in FS at REV. */
1023static svn_error_t *
1024fill_log_entry(svn_log_entry_t *log_entry,
1025               svn_revnum_t rev,
1026               svn_fs_t *fs,
1027               apr_hash_t *prefetched_changes,
1028               svn_boolean_t discover_changed_paths,
1029               const apr_array_header_t *revprops,
1030               svn_repos_authz_func_t authz_read_func,
1031               void *authz_read_baton,
1032               apr_pool_t *pool)
1033{
1034  apr_hash_t *r_props, *changed_paths = NULL;
1035  svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
1036
1037  /* Discover changed paths if the user requested them
1038     or if we need to check that they are readable. */
1039  if ((rev > 0)
1040      && (authz_read_func || discover_changed_paths))
1041    {
1042      svn_fs_root_t *newroot;
1043      svn_error_t *patherr;
1044
1045      SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
1046      patherr = detect_changed(&changed_paths,
1047                               newroot, fs, prefetched_changes,
1048                               authz_read_func, authz_read_baton,
1049                               pool);
1050
1051      if (patherr
1052          && patherr->apr_err == SVN_ERR_AUTHZ_UNREADABLE)
1053        {
1054          /* All changed-paths are unreadable, so clear all fields. */
1055          svn_error_clear(patherr);
1056          changed_paths = NULL;
1057          get_revprops = FALSE;
1058        }
1059      else if (patherr
1060               && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
1061        {
1062          /* At least one changed-path was unreadable, so censor all
1063             but author and date.  (The unreadable paths are already
1064             missing from the hash.) */
1065          svn_error_clear(patherr);
1066          censor_revprops = TRUE;
1067        }
1068      else if (patherr)
1069        return patherr;
1070
1071      /* It may be the case that an authz func was passed in, but
1072         the user still doesn't want to see any changed-paths. */
1073      if (! discover_changed_paths)
1074        changed_paths = NULL;
1075    }
1076
1077  if (get_revprops)
1078    {
1079      /* User is allowed to see at least some revprops. */
1080      SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
1081      if (revprops == NULL)
1082        {
1083          /* Requested all revprops... */
1084          if (censor_revprops)
1085            {
1086              /* ... but we can only return author/date. */
1087              log_entry->revprops = svn_hash__make(pool);
1088              svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1089                            svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR));
1090              svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
1091                            svn_hash_gets(r_props, SVN_PROP_REVISION_DATE));
1092            }
1093          else
1094            /* ... so return all we got. */
1095            log_entry->revprops = r_props;
1096        }
1097      else
1098        {
1099          /* Requested only some revprops... */
1100          int i;
1101          for (i = 0; i < revprops->nelts; i++)
1102            {
1103              char *name = APR_ARRAY_IDX(revprops, i, char *);
1104              svn_string_t *value = svn_hash_gets(r_props, name);
1105              if (censor_revprops
1106                  && !(strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0
1107                       || strcmp(name, SVN_PROP_REVISION_DATE) == 0))
1108                /* ... but we can only return author/date. */
1109                continue;
1110              if (log_entry->revprops == NULL)
1111                log_entry->revprops = svn_hash__make(pool);
1112              svn_hash_sets(log_entry->revprops, name, value);
1113            }
1114        }
1115    }
1116
1117  log_entry->changed_paths = changed_paths;
1118  log_entry->changed_paths2 = changed_paths;
1119  log_entry->revision = rev;
1120
1121  return SVN_NO_ERROR;
1122}
1123
1124/* Send a log message for REV to RECEIVER with its RECEIVER_BATON.
1125
1126   FS is used with REV to fetch the interesting history information,
1127   such as changed paths, revprops, etc.
1128
1129   The detect_changed function is used if either AUTHZ_READ_FUNC is
1130   not NULL, or if DISCOVER_CHANGED_PATHS is TRUE.  See it for details.
1131
1132   If DESCENDING_ORDER is true, send child messages in descending order.
1133
1134   If REVPROPS is NULL, retrieve all revision properties; else, retrieve
1135   only the revision properties named by the (const char *) array elements
1136   (i.e. retrieve none if the array is empty).
1137
1138   LOG_TARGET_HISTORY_AS_MERGEINFO, HANDLING_MERGED_REVISION, and
1139   NESTED_MERGES are as per the arguments of the same name to DO_LOGS.  If
1140   HANDLING_MERGED_REVISION is true and *all* changed paths within REV are
1141   already represented in LOG_TARGET_HISTORY_AS_MERGEINFO, then don't send
1142   the log message for REV.  If SUBTRACTIVE_MERGE is true, then REV was
1143   reverse merged.
1144
1145   If HANDLING_MERGED_REVISIONS is FALSE then ignore NESTED_MERGES.  Otherwise
1146   if NESTED_MERGES is not NULL and REV is contained in it, then don't send
1147   the log for REV, otherwise send it normally and add REV to
1148   NESTED_MERGES. */
1149static svn_error_t *
1150send_log(svn_revnum_t rev,
1151         svn_fs_t *fs,
1152         apr_hash_t *prefetched_changes,
1153         svn_mergeinfo_t log_target_history_as_mergeinfo,
1154         apr_hash_t *nested_merges,
1155         svn_boolean_t discover_changed_paths,
1156         svn_boolean_t subtractive_merge,
1157         svn_boolean_t handling_merged_revision,
1158         const apr_array_header_t *revprops,
1159         svn_boolean_t has_children,
1160         svn_log_entry_receiver_t receiver,
1161         void *receiver_baton,
1162         svn_repos_authz_func_t authz_read_func,
1163         void *authz_read_baton,
1164         apr_pool_t *pool)
1165{
1166  svn_log_entry_t *log_entry;
1167  /* Assume we want to send the log for REV. */
1168  svn_boolean_t found_rev_of_interest = TRUE;
1169
1170  log_entry = svn_log_entry_create(pool);
1171  SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes,
1172                         discover_changed_paths || handling_merged_revision,
1173                         revprops, authz_read_func, authz_read_baton,
1174                         pool));
1175  log_entry->has_children = has_children;
1176  log_entry->subtractive_merge = subtractive_merge;
1177
1178  /* Is REV a merged revision that is already part of
1179     LOG_TARGET_HISTORY_AS_MERGEINFO?  If so then there is no
1180     need to send it, since it already was (or will be) sent. */
1181  if (handling_merged_revision
1182      && log_entry->changed_paths2
1183      && log_target_history_as_mergeinfo
1184      && apr_hash_count(log_target_history_as_mergeinfo))
1185    {
1186      apr_hash_index_t *hi;
1187      apr_pool_t *subpool = svn_pool_create(pool);
1188
1189      /* REV was merged in, but it might already be part of the log target's
1190         natural history, so change our starting assumption. */
1191      found_rev_of_interest = FALSE;
1192
1193      /* Look at each changed path in REV. */
1194      for (hi = apr_hash_first(subpool, log_entry->changed_paths2);
1195           hi;
1196           hi = apr_hash_next(hi))
1197        {
1198          svn_boolean_t path_is_in_history = FALSE;
1199          const char *changed_path = svn__apr_hash_index_key(hi);
1200          apr_hash_index_t *hi2;
1201          apr_pool_t *inner_subpool = svn_pool_create(subpool);
1202
1203          /* Look at each path on the log target's mergeinfo. */
1204          for (hi2 = apr_hash_first(inner_subpool,
1205                                    log_target_history_as_mergeinfo);
1206               hi2;
1207               hi2 = apr_hash_next(hi2))
1208            {
1209              const char *mergeinfo_path =
1210                svn__apr_hash_index_key(hi2);
1211              svn_rangelist_t *rangelist =
1212                svn__apr_hash_index_val(hi2);
1213
1214              /* Check whether CHANGED_PATH at revision REV is a child of
1215                 a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
1216              if (svn_fspath__skip_ancestor(mergeinfo_path, changed_path))
1217                {
1218                  int i;
1219
1220                  for (i = 0; i < rangelist->nelts; i++)
1221                    {
1222                      svn_merge_range_t *range =
1223                        APR_ARRAY_IDX(rangelist, i,
1224                                      svn_merge_range_t *);
1225                      if (rev > range->start && rev <= range->end)
1226                        {
1227                          path_is_in_history = TRUE;
1228                          break;
1229                        }
1230                    }
1231                }
1232              if (path_is_in_history)
1233                break;
1234            }
1235          svn_pool_destroy(inner_subpool);
1236
1237          if (!path_is_in_history)
1238            {
1239              /* If even one path in LOG_ENTRY->CHANGED_PATHS2 is not part of
1240                 LOG_TARGET_HISTORY_AS_MERGEINFO, then we want to send the
1241                 log for REV. */
1242              found_rev_of_interest = TRUE;
1243              break;
1244            }
1245        }
1246      svn_pool_destroy(subpool);
1247    }
1248
1249  /* If we only got changed paths the sake of detecting redundant merged
1250     revisions, then be sure we don't send that info to the receiver. */
1251  if (!discover_changed_paths && handling_merged_revision)
1252    log_entry->changed_paths = log_entry->changed_paths2 = NULL;
1253
1254  /* Send the entry to the receiver, unless it is a redundant merged
1255     revision. */
1256  if (found_rev_of_interest)
1257    {
1258      /* Is REV a merged revision we've already sent? */
1259      if (nested_merges && handling_merged_revision)
1260        {
1261          svn_revnum_t *merged_rev = apr_hash_get(nested_merges, &rev,
1262                                                  sizeof(svn_revnum_t *));
1263
1264          if (merged_rev)
1265            {
1266              /* We already sent REV. */
1267              return SVN_NO_ERROR;
1268            }
1269          else
1270            {
1271              /* NESTED_REVS needs to last across all the send_log, do_logs,
1272                 handle_merged_revisions() recursions, so use the pool it
1273                 was created in at the top of the recursion. */
1274              apr_pool_t *hash_pool = apr_hash_pool_get(nested_merges);
1275              svn_revnum_t *long_lived_rev = apr_palloc(hash_pool,
1276                                                        sizeof(svn_revnum_t));
1277              *long_lived_rev = rev;
1278              apr_hash_set(nested_merges, long_lived_rev,
1279                           sizeof(svn_revnum_t *), long_lived_rev);
1280            }
1281        }
1282
1283      return (*receiver)(receiver_baton, log_entry, pool);
1284    }
1285  else
1286    {
1287      return SVN_NO_ERROR;
1288    }
1289}
1290
1291/* This controls how many history objects we keep open.  For any targets
1292   over this number we have to open and close their histories as needed,
1293   which is CPU intensive, but keeps us from using an unbounded amount of
1294   memory. */
1295#define MAX_OPEN_HISTORIES 32
1296
1297/* Get the histories for PATHS, and store them in *HISTORIES.
1298
1299   If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1300   repository locations as fatal -- just ignore them.  */
1301static svn_error_t *
1302get_path_histories(apr_array_header_t **histories,
1303                   svn_fs_t *fs,
1304                   const apr_array_header_t *paths,
1305                   svn_revnum_t hist_start,
1306                   svn_revnum_t hist_end,
1307                   svn_boolean_t strict_node_history,
1308                   svn_boolean_t ignore_missing_locations,
1309                   svn_repos_authz_func_t authz_read_func,
1310                   void *authz_read_baton,
1311                   apr_pool_t *pool)
1312{
1313  svn_fs_root_t *root;
1314  apr_pool_t *iterpool;
1315  svn_error_t *err;
1316  int i;
1317
1318  /* Create a history object for each path so we can walk through
1319     them all at the same time until we have all changes or LIMIT
1320     is reached.
1321
1322     There is some pool fun going on due to the fact that we have
1323     to hold on to the old pool with the history before we can
1324     get the next history.
1325  */
1326  *histories = apr_array_make(pool, paths->nelts,
1327                              sizeof(struct path_info *));
1328
1329  SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool));
1330
1331  iterpool = svn_pool_create(pool);
1332  for (i = 0; i < paths->nelts; i++)
1333    {
1334      const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
1335      struct path_info *info = apr_palloc(pool,
1336                                          sizeof(struct path_info));
1337
1338      if (authz_read_func)
1339        {
1340          svn_boolean_t readable;
1341
1342          svn_pool_clear(iterpool);
1343
1344          SVN_ERR(authz_read_func(&readable, root, this_path,
1345                                  authz_read_baton, iterpool));
1346          if (! readable)
1347            return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
1348        }
1349
1350      info->path = svn_stringbuf_create(this_path, pool);
1351      info->done = FALSE;
1352      info->history_rev = hist_end;
1353      info->first_time = TRUE;
1354
1355      if (i < MAX_OPEN_HISTORIES)
1356        {
1357          err = svn_fs_node_history(&info->hist, root, this_path, pool);
1358          if (err
1359              && ignore_missing_locations
1360              && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1361                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1362                  err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1363            {
1364              svn_error_clear(err);
1365              continue;
1366            }
1367          SVN_ERR(err);
1368          info->newpool = svn_pool_create(pool);
1369          info->oldpool = svn_pool_create(pool);
1370        }
1371      else
1372        {
1373          info->hist = NULL;
1374          info->oldpool = NULL;
1375          info->newpool = NULL;
1376        }
1377
1378      err = get_history(info, fs,
1379                        strict_node_history,
1380                        authz_read_func, authz_read_baton,
1381                        hist_start, pool);
1382      if (err
1383          && ignore_missing_locations
1384          && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1385              err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1386              err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1387        {
1388          svn_error_clear(err);
1389          continue;
1390        }
1391      SVN_ERR(err);
1392      APR_ARRAY_PUSH(*histories, struct path_info *) = info;
1393    }
1394  svn_pool_destroy(iterpool);
1395
1396  return SVN_NO_ERROR;
1397}
1398
1399/* Remove and return the first item from ARR. */
1400static void *
1401array_pop_front(apr_array_header_t *arr)
1402{
1403  void *item = arr->elts;
1404
1405  if (apr_is_empty_array(arr))
1406    return NULL;
1407
1408  arr->elts += arr->elt_size;
1409  arr->nelts -= 1;
1410  arr->nalloc -= 1;
1411  return item;
1412}
1413
1414/* A struct which represents a single revision range, and the paths which
1415   have mergeinfo in that range. */
1416struct path_list_range
1417{
1418  apr_array_header_t *paths;
1419  svn_merge_range_t range;
1420
1421  /* Is RANGE the result of a reverse merge? */
1422  svn_boolean_t reverse_merge;
1423};
1424
1425/* A struct which represents "inverse mergeinfo", that is, instead of having
1426   a path->revision_range_list mapping, which is the way mergeinfo is commonly
1427   represented, this struct enables a revision_range_list,path tuple, where
1428   the paths can be accessed by revision. */
1429struct rangelist_path
1430{
1431  svn_rangelist_t *rangelist;
1432  const char *path;
1433};
1434
1435/* Comparator function for combine_mergeinfo_path_lists().  Sorts
1436   rangelist_path structs in increasing order based upon starting revision,
1437   then ending revision of the first element in the rangelist.
1438
1439   This does not sort rangelists based upon subsequent elements, only the
1440   first range.  We'll sort any subsequent ranges in the correct order
1441   when they get bumped up to the front by removal of earlier ones, so we
1442   don't really have to sort them here.  See combine_mergeinfo_path_lists()
1443   for details. */
1444static int
1445compare_rangelist_paths(const void *a, const void *b)
1446{
1447  struct rangelist_path *rpa = *((struct rangelist_path *const *) a);
1448  struct rangelist_path *rpb = *((struct rangelist_path *const *) b);
1449  svn_merge_range_t *mra = APR_ARRAY_IDX(rpa->rangelist, 0,
1450                                         svn_merge_range_t *);
1451  svn_merge_range_t *mrb = APR_ARRAY_IDX(rpb->rangelist, 0,
1452                                         svn_merge_range_t *);
1453
1454  if (mra->start < mrb->start)
1455    return -1;
1456  if (mra->start > mrb->start)
1457    return 1;
1458  if (mra->end < mrb->end)
1459    return -1;
1460  if (mra->end > mrb->end)
1461    return 1;
1462
1463  return 0;
1464}
1465
1466/* From MERGEINFO, return in *COMBINED_LIST, allocated in POOL, a list of
1467   'struct path_list_range's.  This list represents the rangelists in
1468   MERGEINFO and each path which has mergeinfo in that range.
1469   If REVERSE_MERGE is true, then MERGEINFO represents mergeinfo removed
1470   as the result of a reverse merge. */
1471static svn_error_t *
1472combine_mergeinfo_path_lists(apr_array_header_t **combined_list,
1473                             svn_mergeinfo_t mergeinfo,
1474                             svn_boolean_t reverse_merge,
1475                             apr_pool_t *pool)
1476{
1477  apr_hash_index_t *hi;
1478  apr_array_header_t *rangelist_paths;
1479  apr_pool_t *subpool = svn_pool_create(pool);
1480
1481  /* Create a list of (revision range, path) tuples from MERGEINFO. */
1482  rangelist_paths = apr_array_make(subpool, apr_hash_count(mergeinfo),
1483                                   sizeof(struct rangelist_path *));
1484  for (hi = apr_hash_first(subpool, mergeinfo); hi;
1485       hi = apr_hash_next(hi))
1486    {
1487      int i;
1488      struct rangelist_path *rp = apr_palloc(subpool, sizeof(*rp));
1489      apr_hash_this(hi, (void *) &rp->path, NULL,
1490                    (void *) &rp->rangelist);
1491      APR_ARRAY_PUSH(rangelist_paths, struct rangelist_path *) = rp;
1492
1493      /* We need to make local copies of the rangelist, since we will be
1494         modifying it, below. */
1495      rp->rangelist = svn_rangelist_dup(rp->rangelist, subpool);
1496
1497      /* Make all of the rangelists inclusive, both start and end. */
1498      for (i = 0; i < rp->rangelist->nelts; i++)
1499        APR_ARRAY_IDX(rp->rangelist, i, svn_merge_range_t *)->start += 1;
1500    }
1501
1502  /* Loop over the (revision range, path) tuples, chopping them into
1503     (revision range, paths) tuples, and appending those to the output
1504     list. */
1505  if (! *combined_list)
1506    *combined_list = apr_array_make(pool, 0, sizeof(struct path_list_range *));
1507
1508  while (rangelist_paths->nelts > 1)
1509    {
1510      svn_revnum_t youngest, next_youngest, tail, youngest_end;
1511      struct path_list_range *plr;
1512      struct rangelist_path *rp;
1513      int num_revs;
1514      int i;
1515
1516      /* First, sort the list such that the start revision of the first
1517         revision arrays are sorted. */
1518      qsort(rangelist_paths->elts, rangelist_paths->nelts,
1519            rangelist_paths->elt_size, compare_rangelist_paths);
1520
1521      /* Next, find the number of revision ranges which start with the same
1522         revision. */
1523      rp = APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1524      youngest =
1525        APR_ARRAY_IDX(rp->rangelist, 0, struct svn_merge_range_t *)->start;
1526      next_youngest = youngest;
1527      for (num_revs = 1; next_youngest == youngest; num_revs++)
1528        {
1529          if (num_revs == rangelist_paths->nelts)
1530            {
1531              num_revs += 1;
1532              break;
1533            }
1534          rp = APR_ARRAY_IDX(rangelist_paths, num_revs,
1535                             struct rangelist_path *);
1536          next_youngest = APR_ARRAY_IDX(rp->rangelist, 0,
1537                                        struct svn_merge_range_t *)->start;
1538        }
1539      num_revs -= 1;
1540
1541      /* The start of the new range will be YOUNGEST, and we now find the end
1542         of the new range, which should be either one less than the next
1543         earliest start of a rangelist, or the end of the first rangelist. */
1544      youngest_end =
1545        APR_ARRAY_IDX(APR_ARRAY_IDX(rangelist_paths, 0,
1546                                    struct rangelist_path *)->rangelist,
1547                      0, svn_merge_range_t *)->end;
1548      if ( (next_youngest == youngest) || (youngest_end < next_youngest) )
1549        tail = youngest_end;
1550      else
1551        tail = next_youngest - 1;
1552
1553      /* Insert the (earliest, tail) tuple into the output list, along with
1554         a list of paths which match it. */
1555      plr = apr_palloc(pool, sizeof(*plr));
1556      plr->reverse_merge = reverse_merge;
1557      plr->range.start = youngest;
1558      plr->range.end = tail;
1559      plr->paths = apr_array_make(pool, num_revs, sizeof(const char *));
1560      for (i = 0; i < num_revs; i++)
1561        APR_ARRAY_PUSH(plr->paths, const char *) =
1562          APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *)->path;
1563      APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1564
1565      /* Now, check to see which (rangelist path) combinations we can remove,
1566         and do so. */
1567      for (i = 0; i < num_revs; i++)
1568        {
1569          svn_merge_range_t *range;
1570          rp = APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *);
1571          range = APR_ARRAY_IDX(rp->rangelist, 0, svn_merge_range_t *);
1572
1573          /* Set the start of the range to beyond the end of the range we
1574             just built.  If the range is now "inverted", we can get pop it
1575             off the list. */
1576          range->start = tail + 1;
1577          if (range->start > range->end)
1578            {
1579              if (rp->rangelist->nelts == 1)
1580                {
1581                  /* The range is the only on its list, so we should remove
1582                     the entire rangelist_path, adjusting our loop control
1583                     variables appropriately. */
1584                  array_pop_front(rangelist_paths);
1585                  i--;
1586                  num_revs--;
1587                }
1588              else
1589                {
1590                  /* We have more than one range on the list, so just remove
1591                     the first one. */
1592                  array_pop_front(rp->rangelist);
1593                }
1594            }
1595        }
1596    }
1597
1598  /* Finally, add the last remaining (revision range, path) to the output
1599     list. */
1600  if (rangelist_paths->nelts > 0)
1601    {
1602      struct rangelist_path *first_rp =
1603        APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1604      while (first_rp->rangelist->nelts > 0)
1605        {
1606          struct path_list_range *plr = apr_palloc(pool, sizeof(*plr));
1607
1608          plr->reverse_merge = reverse_merge;
1609          plr->paths = apr_array_make(pool, 1, sizeof(const char *));
1610          APR_ARRAY_PUSH(plr->paths, const char *) = first_rp->path;
1611          plr->range = *APR_ARRAY_IDX(first_rp->rangelist, 0,
1612                                      svn_merge_range_t *);
1613          array_pop_front(first_rp->rangelist);
1614          APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1615        }
1616    }
1617
1618  svn_pool_destroy(subpool);
1619
1620  return SVN_NO_ERROR;
1621}
1622
1623
1624/* Pity that C is so ... linear. */
1625static svn_error_t *
1626do_logs(svn_fs_t *fs,
1627        const apr_array_header_t *paths,
1628        svn_mergeinfo_t log_target_history_as_mergeinfo,
1629        svn_mergeinfo_t processed,
1630        apr_hash_t *nested_merges,
1631        svn_revnum_t hist_start,
1632        svn_revnum_t hist_end,
1633        int limit,
1634        svn_boolean_t discover_changed_paths,
1635        svn_boolean_t strict_node_history,
1636        svn_boolean_t include_merged_revisions,
1637        svn_boolean_t handling_merged_revisions,
1638        svn_boolean_t subtractive_merge,
1639        svn_boolean_t ignore_missing_locations,
1640        const apr_array_header_t *revprops,
1641        svn_boolean_t descending_order,
1642        svn_log_entry_receiver_t receiver,
1643        void *receiver_baton,
1644        svn_repos_authz_func_t authz_read_func,
1645        void *authz_read_baton,
1646        apr_pool_t *pool);
1647
1648/* Comparator function for handle_merged_revisions().  Sorts path_list_range
1649   structs in increasing order based on the struct's RANGE.START revision,
1650   then RANGE.END revision. */
1651static int
1652compare_path_list_range(const void *a, const void *b)
1653{
1654  struct path_list_range *plr_a = *((struct path_list_range *const *) a);
1655  struct path_list_range *plr_b = *((struct path_list_range *const *) b);
1656
1657  if (plr_a->range.start < plr_b->range.start)
1658    return -1;
1659  if (plr_a->range.start > plr_b->range.start)
1660    return 1;
1661  if (plr_a->range.end < plr_b->range.end)
1662    return -1;
1663  if (plr_a->range.end > plr_b->range.end)
1664    return 1;
1665
1666  return 0;
1667}
1668
1669/* Examine the ADDED_MERGEINFO and DELETED_MERGEINFO for revision REV in FS
1670   (as collected by examining paths of interest to a log operation), and
1671   determine which revisions to report as having been merged or reverse-merged
1672   via the commit resulting in REV.
1673
1674   Silently ignore some failures to find the revisions mentioned in the
1675   added/deleted mergeinfos, as might happen if there is invalid mergeinfo.
1676
1677   Other parameters are as described by do_logs(), around which this
1678   is a recursion wrapper. */
1679static svn_error_t *
1680handle_merged_revisions(svn_revnum_t rev,
1681                        svn_fs_t *fs,
1682                        svn_mergeinfo_t log_target_history_as_mergeinfo,
1683                        apr_hash_t *nested_merges,
1684                        svn_mergeinfo_t processed,
1685                        svn_mergeinfo_t added_mergeinfo,
1686                        svn_mergeinfo_t deleted_mergeinfo,
1687                        svn_boolean_t discover_changed_paths,
1688                        svn_boolean_t strict_node_history,
1689                        const apr_array_header_t *revprops,
1690                        svn_log_entry_receiver_t receiver,
1691                        void *receiver_baton,
1692                        svn_repos_authz_func_t authz_read_func,
1693                        void *authz_read_baton,
1694                        apr_pool_t *pool)
1695{
1696  apr_array_header_t *combined_list = NULL;
1697  svn_log_entry_t *empty_log_entry;
1698  apr_pool_t *iterpool;
1699  int i;
1700
1701  if (apr_hash_count(added_mergeinfo) == 0
1702      && apr_hash_count(deleted_mergeinfo) == 0)
1703    return SVN_NO_ERROR;
1704
1705  if (apr_hash_count(added_mergeinfo))
1706    SVN_ERR(combine_mergeinfo_path_lists(&combined_list, added_mergeinfo,
1707                                          FALSE, pool));
1708
1709  if (apr_hash_count(deleted_mergeinfo))
1710    SVN_ERR(combine_mergeinfo_path_lists(&combined_list, deleted_mergeinfo,
1711                                          TRUE, pool));
1712
1713  SVN_ERR_ASSERT(combined_list != NULL);
1714  qsort(combined_list->elts, combined_list->nelts,
1715        combined_list->elt_size, compare_path_list_range);
1716
1717  /* Because the combined_lists are ordered youngest to oldest,
1718     iterate over them in reverse. */
1719  iterpool = svn_pool_create(pool);
1720  for (i = combined_list->nelts - 1; i >= 0; i--)
1721    {
1722      struct path_list_range *pl_range
1723        = APR_ARRAY_IDX(combined_list, i, struct path_list_range *);
1724
1725      svn_pool_clear(iterpool);
1726      SVN_ERR(do_logs(fs, pl_range->paths, log_target_history_as_mergeinfo,
1727                      processed, nested_merges,
1728                      pl_range->range.start, pl_range->range.end, 0,
1729                      discover_changed_paths, strict_node_history,
1730                      TRUE, pl_range->reverse_merge, TRUE, TRUE,
1731                      revprops, TRUE, receiver, receiver_baton,
1732                      authz_read_func, authz_read_baton, iterpool));
1733    }
1734  svn_pool_destroy(iterpool);
1735
1736  /* Send the empty revision.  */
1737  empty_log_entry = svn_log_entry_create(pool);
1738  empty_log_entry->revision = SVN_INVALID_REVNUM;
1739  return (*receiver)(receiver_baton, empty_log_entry, pool);
1740}
1741
1742/* This is used by do_logs to differentiate between forward and
1743   reverse merges. */
1744struct added_deleted_mergeinfo
1745{
1746  svn_mergeinfo_t added_mergeinfo;
1747  svn_mergeinfo_t deleted_mergeinfo;
1748};
1749
1750/* Reduce the search range PATHS, HIST_START, HIST_END by removing
1751   parts already covered by PROCESSED.  If reduction is possible
1752   elements may be removed from PATHS and *START_REDUCED and
1753   *END_REDUCED may be set to a narrower range. */
1754static svn_error_t *
1755reduce_search(apr_array_header_t *paths,
1756              svn_revnum_t *hist_start,
1757              svn_revnum_t *hist_end,
1758              svn_mergeinfo_t processed,
1759              apr_pool_t *scratch_pool)
1760{
1761  /* We add 1 to end to compensate for store_search */
1762  svn_revnum_t start = *hist_start <= *hist_end ? *hist_start : *hist_end;
1763  svn_revnum_t end = *hist_start <= *hist_end ? *hist_end + 1 : *hist_start + 1;
1764  int i;
1765
1766  for (i = 0; i < paths->nelts; ++i)
1767    {
1768      const char *path = APR_ARRAY_IDX(paths, i, const char *);
1769      svn_rangelist_t *ranges = svn_hash_gets(processed, path);
1770      int j;
1771
1772      if (!ranges)
1773        continue;
1774
1775      /* ranges is ordered, could we use some sort of binary search
1776         rather than iterating? */
1777      for (j = 0; j < ranges->nelts; ++j)
1778        {
1779          svn_merge_range_t *range = APR_ARRAY_IDX(ranges, j,
1780                                                   svn_merge_range_t *);
1781          if (range->start <= start && range->end >= end)
1782            {
1783              for (j = i; j < paths->nelts - 1; ++j)
1784                APR_ARRAY_IDX(paths, j, const char *)
1785                  = APR_ARRAY_IDX(paths, j + 1, const char *);
1786
1787              --paths->nelts;
1788              --i;
1789              break;
1790            }
1791
1792          /* If there is only one path then we also check for a
1793             partial overlap rather than the full overlap above, and
1794             reduce the [hist_start, hist_end] range rather than
1795             dropping the path. */
1796          if (paths->nelts == 1)
1797            {
1798              if (range->start <= start && range->end > start)
1799                {
1800                  if (start == *hist_start)
1801                    *hist_start = range->end - 1;
1802                  else
1803                    *hist_end = range->end - 1;
1804                  break;
1805                }
1806              if (range->start < end && range->end >= end)
1807                {
1808                  if (start == *hist_start)
1809                    *hist_end = range->start;
1810                  else
1811                    *hist_start = range->start;
1812                  break;
1813                }
1814            }
1815        }
1816    }
1817
1818  return SVN_NO_ERROR;
1819}
1820
1821/* Extend PROCESSED to cover PATHS from HIST_START to HIST_END */
1822static svn_error_t *
1823store_search(svn_mergeinfo_t processed,
1824             const apr_array_header_t *paths,
1825             svn_revnum_t hist_start,
1826             svn_revnum_t hist_end,
1827             apr_pool_t *scratch_pool)
1828{
1829  /* We add 1 to end so that we can use the mergeinfo API to handle
1830     singe revisions where HIST_START is equal to HIST_END. */
1831  svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end;
1832  svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1;
1833  svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool);
1834  apr_pool_t *processed_pool = apr_hash_pool_get(processed);
1835  int i;
1836
1837  for (i = 0; i < paths->nelts; ++i)
1838    {
1839      const char *path = APR_ARRAY_IDX(paths, i, const char *);
1840      svn_rangelist_t *ranges = apr_array_make(processed_pool, 1,
1841                                               sizeof(svn_merge_range_t*));
1842      svn_merge_range_t *range = apr_palloc(processed_pool,
1843                                            sizeof(svn_merge_range_t));
1844
1845      range->start = start;
1846      range->end = end;
1847      range->inheritable = TRUE;
1848      APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range;
1849      svn_hash_sets(mergeinfo, apr_pstrdup(processed_pool, path), ranges);
1850    }
1851  SVN_ERR(svn_mergeinfo_merge2(processed, mergeinfo,
1852                               apr_hash_pool_get(processed), scratch_pool));
1853
1854  return SVN_NO_ERROR;
1855}
1856
1857/* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke
1858   RECEIVER with RECEIVER_BATON on them.  If DESCENDING_ORDER is TRUE, send
1859   the logs back as we find them, else buffer the logs and send them back
1860   in youngest->oldest order.
1861
1862   If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1863   repository locations as fatal -- just ignore them.
1864
1865   If LOG_TARGET_HISTORY_AS_MERGEINFO is not NULL then it contains mergeinfo
1866   representing the history of PATHS between HIST_START and HIST_END.
1867
1868   If HANDLING_MERGED_REVISIONS is TRUE then this is a recursive call for
1869   merged revisions, see INCLUDE_MERGED_REVISIONS argument to
1870   svn_repos_get_logs4().  If SUBTRACTIVE_MERGE is true, then this is a
1871   recursive call for reverse merged revisions.
1872
1873   If NESTED_MERGES is not NULL then it is a hash of revisions (svn_revnum_t *
1874   mapped to svn_revnum_t *) for logs that were previously sent.  On the first
1875   call to do_logs it should always be NULL.  If INCLUDE_MERGED_REVISIONS is
1876   TRUE, then NESTED_MERGES will be created on the first call to do_logs,
1877   allocated in POOL.  It is then shared across
1878   do_logs()/send_logs()/handle_merge_revisions() recursions, see also the
1879   argument of the same name in send_logs().
1880
1881   PROCESSED is a mergeinfo hash that represents the paths and
1882   revisions that have already been searched.  Allocated like
1883   NESTED_MERGES above.
1884
1885   All other parameters are the same as svn_repos_get_logs4().
1886 */
1887static svn_error_t *
1888do_logs(svn_fs_t *fs,
1889        const apr_array_header_t *paths,
1890        svn_mergeinfo_t log_target_history_as_mergeinfo,
1891        svn_mergeinfo_t processed,
1892        apr_hash_t *nested_merges,
1893        svn_revnum_t hist_start,
1894        svn_revnum_t hist_end,
1895        int limit,
1896        svn_boolean_t discover_changed_paths,
1897        svn_boolean_t strict_node_history,
1898        svn_boolean_t include_merged_revisions,
1899        svn_boolean_t subtractive_merge,
1900        svn_boolean_t handling_merged_revisions,
1901        svn_boolean_t ignore_missing_locations,
1902        const apr_array_header_t *revprops,
1903        svn_boolean_t descending_order,
1904        svn_log_entry_receiver_t receiver,
1905        void *receiver_baton,
1906        svn_repos_authz_func_t authz_read_func,
1907        void *authz_read_baton,
1908        apr_pool_t *pool)
1909{
1910  apr_pool_t *iterpool;
1911  apr_pool_t *subpool = NULL;
1912  apr_array_header_t *revs = NULL;
1913  apr_hash_t *rev_mergeinfo = NULL;
1914  svn_revnum_t current;
1915  apr_array_header_t *histories;
1916  svn_boolean_t any_histories_left = TRUE;
1917  int send_count = 0;
1918  int i;
1919
1920  if (processed)
1921    {
1922      /* Casting away const. This only happens on recursive calls when
1923         it is known to be safe because we allocated paths. */
1924      SVN_ERR(reduce_search((apr_array_header_t *)paths, &hist_start, &hist_end,
1925                            processed, pool));
1926    }
1927
1928  if (!paths->nelts)
1929    return SVN_NO_ERROR;
1930
1931  if (processed)
1932    SVN_ERR(store_search(processed, paths, hist_start, hist_end, pool));
1933
1934  /* We have a list of paths and a revision range.  But we don't care
1935     about all the revisions in the range -- only the ones in which
1936     one of our paths was changed.  So let's go figure out which
1937     revisions contain real changes to at least one of our paths.  */
1938  SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end,
1939                             strict_node_history, ignore_missing_locations,
1940                             authz_read_func, authz_read_baton, pool));
1941
1942  /* Loop through all the revisions in the range and add any
1943     where a path was changed to the array, or if they wanted
1944     history in reverse order just send it to them right away. */
1945  iterpool = svn_pool_create(pool);
1946  for (current = hist_end;
1947       any_histories_left;
1948       current = next_history_rev(histories))
1949    {
1950      svn_boolean_t changed = FALSE;
1951      any_histories_left = FALSE;
1952      svn_pool_clear(iterpool);
1953
1954      for (i = 0; i < histories->nelts; i++)
1955        {
1956          struct path_info *info = APR_ARRAY_IDX(histories, i,
1957                                                 struct path_info *);
1958
1959          /* Check history for this path in current rev. */
1960          SVN_ERR(check_history(&changed, info, fs, current,
1961                                strict_node_history, authz_read_func,
1962                                authz_read_baton, hist_start, pool));
1963          if (! info->done)
1964            any_histories_left = TRUE;
1965        }
1966
1967      /* If any of the paths changed in this rev then add or send it. */
1968      if (changed)
1969        {
1970          svn_mergeinfo_t added_mergeinfo = NULL;
1971          svn_mergeinfo_t deleted_mergeinfo = NULL;
1972          svn_boolean_t has_children = FALSE;
1973          apr_hash_t *changes = NULL;
1974
1975          /* If we're including merged revisions, we need to calculate
1976             the mergeinfo deltas committed in this revision to our
1977             various paths. */
1978          if (include_merged_revisions)
1979            {
1980              apr_array_header_t *cur_paths =
1981                apr_array_make(iterpool, paths->nelts, sizeof(const char *));
1982
1983              /* Get the current paths of our history objects so we can
1984                 query mergeinfo. */
1985              /* ### TODO: Should this be ignoring depleted history items? */
1986              for (i = 0; i < histories->nelts; i++)
1987                {
1988                  struct path_info *info = APR_ARRAY_IDX(histories, i,
1989                                                         struct path_info *);
1990                  APR_ARRAY_PUSH(cur_paths, const char *) = info->path->data;
1991                }
1992              SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo,
1993                                                     &deleted_mergeinfo,
1994                                                     &changes,
1995                                                     fs, cur_paths,
1996                                                     current, iterpool,
1997                                                     iterpool));
1998              has_children = (apr_hash_count(added_mergeinfo) > 0
1999                              || apr_hash_count(deleted_mergeinfo) > 0);
2000            }
2001
2002          /* If our caller wants logs in descending order, we can send
2003             'em now (because that's the order we're crawling history
2004             in anyway). */
2005          if (descending_order)
2006            {
2007              SVN_ERR(send_log(current, fs, changes,
2008                               log_target_history_as_mergeinfo, nested_merges,
2009                               discover_changed_paths,
2010                               subtractive_merge, handling_merged_revisions,
2011                               revprops, has_children,
2012                               receiver, receiver_baton,
2013                               authz_read_func, authz_read_baton, iterpool));
2014
2015              if (has_children) /* Implies include_merged_revisions == TRUE */
2016                {
2017                  if (!nested_merges)
2018                    {
2019                      /* We're at the start of the recursion stack, create a
2020                         single hash to be shared across all of the merged
2021                         recursions so we can track and squelch duplicates. */
2022                      subpool = svn_pool_create(pool);
2023                      nested_merges = svn_hash__make(subpool);
2024                      processed = svn_hash__make(subpool);
2025                    }
2026
2027                  SVN_ERR(handle_merged_revisions(
2028                    current, fs,
2029                    log_target_history_as_mergeinfo, nested_merges,
2030                    processed,
2031                    added_mergeinfo, deleted_mergeinfo,
2032                    discover_changed_paths,
2033                    strict_node_history,
2034                    revprops,
2035                    receiver, receiver_baton,
2036                    authz_read_func,
2037                    authz_read_baton,
2038                    iterpool));
2039                }
2040              if (limit && ++send_count >= limit)
2041                break;
2042            }
2043          /* Otherwise, the caller wanted logs in ascending order, so
2044             we have to buffer up a list of revs and (if doing
2045             mergeinfo) a hash of related mergeinfo deltas, and
2046             process them later. */
2047          else
2048            {
2049              if (! revs)
2050                revs = apr_array_make(pool, 64, sizeof(svn_revnum_t));
2051              APR_ARRAY_PUSH(revs, svn_revnum_t) = current;
2052
2053              if (added_mergeinfo || deleted_mergeinfo)
2054                {
2055                  svn_revnum_t *cur_rev = apr_pcalloc(pool, sizeof(*cur_rev));
2056                  struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2057                    apr_palloc(pool, sizeof(*add_and_del_mergeinfo));
2058
2059                  if (added_mergeinfo)
2060                    add_and_del_mergeinfo->added_mergeinfo =
2061                      svn_mergeinfo_dup(added_mergeinfo, pool);
2062
2063                  if (deleted_mergeinfo)
2064                    add_and_del_mergeinfo->deleted_mergeinfo =
2065                      svn_mergeinfo_dup(deleted_mergeinfo, pool);
2066
2067                  *cur_rev = current;
2068                  if (! rev_mergeinfo)
2069                    rev_mergeinfo = svn_hash__make(pool);
2070                  apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev),
2071                               add_and_del_mergeinfo);
2072                }
2073            }
2074        }
2075    }
2076  svn_pool_destroy(iterpool);
2077
2078  if (subpool)
2079    {
2080      nested_merges = NULL;
2081      svn_pool_destroy(subpool);
2082    }
2083
2084  if (revs)
2085    {
2086      /* Work loop for processing the revisions we found since they wanted
2087         history in forward order. */
2088      iterpool = svn_pool_create(pool);
2089      for (i = 0; i < revs->nelts; ++i)
2090        {
2091          svn_mergeinfo_t added_mergeinfo;
2092          svn_mergeinfo_t deleted_mergeinfo;
2093          svn_boolean_t has_children = FALSE;
2094
2095          svn_pool_clear(iterpool);
2096          current = APR_ARRAY_IDX(revs, revs->nelts - i - 1, svn_revnum_t);
2097
2098          /* If we've got a hash of revision mergeinfo (which can only
2099             happen if INCLUDE_MERGED_REVISIONS was set), we check to
2100             see if this revision is one which merged in other
2101             revisions we need to handle recursively. */
2102          if (rev_mergeinfo)
2103            {
2104              struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2105                apr_hash_get(rev_mergeinfo, &current, sizeof(svn_revnum_t));
2106              added_mergeinfo = add_and_del_mergeinfo->added_mergeinfo;
2107              deleted_mergeinfo = add_and_del_mergeinfo->deleted_mergeinfo;
2108              has_children = (apr_hash_count(added_mergeinfo) > 0
2109                              || apr_hash_count(deleted_mergeinfo) > 0);
2110            }
2111
2112          SVN_ERR(send_log(current, fs, NULL,
2113                           log_target_history_as_mergeinfo, nested_merges,
2114                           discover_changed_paths, subtractive_merge,
2115                           handling_merged_revisions, revprops, has_children,
2116                           receiver, receiver_baton, authz_read_func,
2117                           authz_read_baton, iterpool));
2118          if (has_children)
2119            {
2120              if (!nested_merges)
2121                {
2122                  subpool = svn_pool_create(pool);
2123                  nested_merges = svn_hash__make(subpool);
2124                }
2125
2126              SVN_ERR(handle_merged_revisions(current, fs,
2127                                              log_target_history_as_mergeinfo,
2128                                              nested_merges,
2129                                              processed,
2130                                              added_mergeinfo,
2131                                              deleted_mergeinfo,
2132                                              discover_changed_paths,
2133                                              strict_node_history, revprops,
2134                                              receiver, receiver_baton,
2135                                              authz_read_func,
2136                                              authz_read_baton,
2137                                              iterpool));
2138            }
2139          if (limit && i + 1 >= limit)
2140            break;
2141        }
2142      svn_pool_destroy(iterpool);
2143    }
2144
2145  return SVN_NO_ERROR;
2146}
2147
2148struct location_segment_baton
2149{
2150  apr_array_header_t *history_segments;
2151  apr_pool_t *pool;
2152};
2153
2154/* svn_location_segment_receiver_t implementation for svn_repos_get_logs4. */
2155static svn_error_t *
2156location_segment_receiver(svn_location_segment_t *segment,
2157                          void *baton,
2158                          apr_pool_t *pool)
2159{
2160  struct location_segment_baton *b = baton;
2161
2162  APR_ARRAY_PUSH(b->history_segments, svn_location_segment_t *) =
2163    svn_location_segment_dup(segment, b->pool);
2164
2165  return SVN_NO_ERROR;
2166}
2167
2168
2169/* Populate *PATHS_HISTORY_MERGEINFO with mergeinfo representing the combined
2170   history of each path in PATHS between START_REV and END_REV in REPOS's
2171   filesystem.  START_REV and END_REV must be valid revisions.  RESULT_POOL
2172   is used to allocate *PATHS_HISTORY_MERGEINFO, SCRATCH_POOL is used for all
2173   other (temporary) allocations.  Other parameters are the same as
2174   svn_repos_get_logs4(). */
2175static svn_error_t *
2176get_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo,
2177                               svn_repos_t *repos,
2178                               const apr_array_header_t *paths,
2179                               svn_revnum_t start_rev,
2180                               svn_revnum_t end_rev,
2181                               svn_repos_authz_func_t authz_read_func,
2182                               void *authz_read_baton,
2183                               apr_pool_t *result_pool,
2184                               apr_pool_t *scratch_pool)
2185{
2186  int i;
2187  svn_mergeinfo_t path_history_mergeinfo;
2188  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2189
2190  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_rev));
2191  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_rev));
2192
2193  /* Ensure START_REV is the youngest revision, as required by
2194     svn_repos_node_location_segments, for which this is an iterative
2195     wrapper. */
2196  if (start_rev < end_rev)
2197    {
2198      svn_revnum_t tmp_rev = start_rev;
2199      start_rev = end_rev;
2200      end_rev = tmp_rev;
2201    }
2202
2203  *paths_history_mergeinfo = svn_hash__make(result_pool);
2204
2205  for (i = 0; i < paths->nelts; i++)
2206    {
2207      const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
2208      struct location_segment_baton loc_seg_baton;
2209
2210      svn_pool_clear(iterpool);
2211      loc_seg_baton.pool = scratch_pool;
2212      loc_seg_baton.history_segments =
2213        apr_array_make(iterpool, 4, sizeof(svn_location_segment_t *));
2214
2215      SVN_ERR(svn_repos_node_location_segments(repos, this_path, start_rev,
2216                                               start_rev, end_rev,
2217                                               location_segment_receiver,
2218                                               &loc_seg_baton,
2219                                               authz_read_func,
2220                                               authz_read_baton,
2221                                               iterpool));
2222
2223      SVN_ERR(svn_mergeinfo__mergeinfo_from_segments(
2224        &path_history_mergeinfo, loc_seg_baton.history_segments, iterpool));
2225      SVN_ERR(svn_mergeinfo_merge2(*paths_history_mergeinfo,
2226                                   svn_mergeinfo_dup(path_history_mergeinfo,
2227                                                     result_pool),
2228                                   result_pool, iterpool));
2229    }
2230  svn_pool_destroy(iterpool);
2231  return SVN_NO_ERROR;
2232}
2233
2234svn_error_t *
2235svn_repos_get_logs4(svn_repos_t *repos,
2236                    const apr_array_header_t *paths,
2237                    svn_revnum_t start,
2238                    svn_revnum_t end,
2239                    int limit,
2240                    svn_boolean_t discover_changed_paths,
2241                    svn_boolean_t strict_node_history,
2242                    svn_boolean_t include_merged_revisions,
2243                    const apr_array_header_t *revprops,
2244                    svn_repos_authz_func_t authz_read_func,
2245                    void *authz_read_baton,
2246                    svn_log_entry_receiver_t receiver,
2247                    void *receiver_baton,
2248                    apr_pool_t *pool)
2249{
2250  svn_revnum_t head = SVN_INVALID_REVNUM;
2251  svn_fs_t *fs = repos->fs;
2252  svn_boolean_t descending_order;
2253  svn_mergeinfo_t paths_history_mergeinfo = NULL;
2254
2255  /* Setup log range. */
2256  SVN_ERR(svn_fs_youngest_rev(&head, fs, pool));
2257
2258  if (! SVN_IS_VALID_REVNUM(start))
2259    start = head;
2260
2261  if (! SVN_IS_VALID_REVNUM(end))
2262    end = head;
2263
2264  /* Check that revisions are sane before ever invoking receiver. */
2265  if (start > head)
2266    return svn_error_createf
2267      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2268       _("No such revision %ld"), start);
2269  if (end > head)
2270    return svn_error_createf
2271      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2272       _("No such revision %ld"), end);
2273
2274  /* Ensure a youngest-to-oldest revision crawl ordering using our
2275     (possibly sanitized) range values. */
2276  descending_order = start >= end;
2277  if (descending_order)
2278    {
2279      svn_revnum_t tmp_rev = start;
2280      start = end;
2281      end = tmp_rev;
2282    }
2283
2284  if (! paths)
2285    paths = apr_array_make(pool, 0, sizeof(const char *));
2286
2287  /* If we're not including merged revisions, and we were given no
2288     paths or a single empty (or "/") path, then we can bypass a bunch
2289     of complexity because we already know in which revisions the root
2290     directory was changed -- all of them.  */
2291  if ((! include_merged_revisions)
2292      && ((! paths->nelts)
2293          || ((paths->nelts == 1)
2294              && (svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *))
2295                  || (strcmp(APR_ARRAY_IDX(paths, 0, const char *),
2296                             "/") == 0)))))
2297    {
2298      apr_uint64_t send_count = 0;
2299      int i;
2300      apr_pool_t *iterpool = svn_pool_create(pool);
2301
2302      /* If we are provided an authz callback function, use it to
2303         verify that the user has read access to the root path in the
2304         first of our revisions.
2305
2306         ### FIXME:  Strictly speaking, we should be checking this
2307         ### access in every revision along the line.  But currently,
2308         ### there are no known authz implementations which concern
2309         ### themselves with per-revision access.  */
2310      if (authz_read_func)
2311        {
2312          svn_boolean_t readable;
2313          svn_fs_root_t *rev_root;
2314
2315          SVN_ERR(svn_fs_revision_root(&rev_root, fs,
2316                                       descending_order ? end : start, pool));
2317          SVN_ERR(authz_read_func(&readable, rev_root, "",
2318                                  authz_read_baton, pool));
2319          if (! readable)
2320            return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
2321        }
2322
2323      send_count = end - start + 1;
2324      if (limit && send_count > limit)
2325        send_count = limit;
2326      for (i = 0; i < send_count; ++i)
2327        {
2328          svn_revnum_t rev;
2329
2330          svn_pool_clear(iterpool);
2331
2332          if (descending_order)
2333            rev = end - i;
2334          else
2335            rev = start + i;
2336          SVN_ERR(send_log(rev, fs, NULL, NULL, NULL,
2337                           discover_changed_paths, FALSE,
2338                           FALSE, revprops, FALSE, receiver,
2339                           receiver_baton, authz_read_func,
2340                           authz_read_baton, iterpool));
2341        }
2342      svn_pool_destroy(iterpool);
2343
2344      return SVN_NO_ERROR;
2345    }
2346
2347  /* If we are including merged revisions, then create mergeinfo that
2348     represents all of PATHS' history between START and END.  We will use
2349     this later to squelch duplicate log revisions that might exist in
2350     both natural history and merged-in history.  See
2351     http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */
2352  if (include_merged_revisions)
2353    {
2354      apr_pool_t *subpool = svn_pool_create(pool);
2355
2356      SVN_ERR(get_paths_history_as_mergeinfo(&paths_history_mergeinfo,
2357                                             repos, paths, start, end,
2358                                             authz_read_func,
2359                                             authz_read_baton,
2360                                             pool, subpool));
2361      svn_pool_destroy(subpool);
2362    }
2363
2364  return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL, start, end,
2365                 limit, discover_changed_paths, strict_node_history,
2366                 include_merged_revisions, FALSE, FALSE, FALSE, revprops,
2367                 descending_order, receiver, receiver_baton,
2368                 authz_read_func, authz_read_baton, pool);
2369}
2370