1251881Speter/* log.c --- retrieving log messages
2251881Speter *
3251881Speter * ====================================================================
4251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
5251881Speter *    or more contributor license agreements.  See the NOTICE file
6251881Speter *    distributed with this work for additional information
7251881Speter *    regarding copyright ownership.  The ASF licenses this file
8251881Speter *    to you under the Apache License, Version 2.0 (the
9251881Speter *    "License"); you may not use this file except in compliance
10251881Speter *    with the License.  You may obtain a copy of the License at
11251881Speter *
12251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
13251881Speter *
14251881Speter *    Unless required by applicable law or agreed to in writing,
15251881Speter *    software distributed under the License is distributed on an
16251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17251881Speter *    KIND, either express or implied.  See the License for the
18251881Speter *    specific language governing permissions and limitations
19251881Speter *    under the License.
20251881Speter * ====================================================================
21251881Speter */
22251881Speter
23251881Speter
24251881Speter#include <stdlib.h>
25251881Speter#define APR_WANT_STRFUNC
26251881Speter#include <apr_want.h>
27251881Speter
28251881Speter#include "svn_compat.h"
29251881Speter#include "svn_private_config.h"
30251881Speter#include "svn_hash.h"
31251881Speter#include "svn_pools.h"
32251881Speter#include "svn_error.h"
33251881Speter#include "svn_path.h"
34251881Speter#include "svn_fs.h"
35251881Speter#include "svn_repos.h"
36251881Speter#include "svn_string.h"
37251881Speter#include "svn_sorts.h"
38251881Speter#include "svn_props.h"
39251881Speter#include "svn_mergeinfo.h"
40251881Speter#include "repos.h"
41251881Speter#include "private/svn_fspath.h"
42299742Sdim#include "private/svn_fs_private.h"
43251881Speter#include "private/svn_mergeinfo_private.h"
44251881Speter#include "private/svn_subr_private.h"
45299742Sdim#include "private/svn_sorts_private.h"
46251881Speter
47251881Speter
48251881Speter
49251881Spetersvn_error_t *
50251881Spetersvn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
51251881Speter                                svn_repos_t *repos,
52251881Speter                                svn_revnum_t revision,
53251881Speter                                svn_repos_authz_func_t authz_read_func,
54251881Speter                                void *authz_read_baton,
55251881Speter                                apr_pool_t *pool)
56251881Speter{
57251881Speter  svn_fs_t *fs = svn_repos_fs(repos);
58251881Speter  svn_fs_root_t *rev_root;
59251881Speter  apr_hash_t *changes;
60251881Speter  apr_hash_index_t *hi;
61251881Speter  svn_boolean_t found_readable = FALSE;
62251881Speter  svn_boolean_t found_unreadable = FALSE;
63251881Speter  apr_pool_t *subpool;
64251881Speter
65251881Speter  /* By default, we'll grant full read access to REVISION. */
66251881Speter  *access_level = svn_repos_revision_access_full;
67251881Speter
68251881Speter  /* No auth-checking function?  We're done. */
69251881Speter  if (! authz_read_func)
70251881Speter    return SVN_NO_ERROR;
71251881Speter
72251881Speter  /* Fetch the changes associated with REVISION. */
73251881Speter  SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool));
74251881Speter  SVN_ERR(svn_fs_paths_changed2(&changes, rev_root, pool));
75251881Speter
76251881Speter  /* No changed paths?  We're done. */
77251881Speter  if (apr_hash_count(changes) == 0)
78251881Speter    return SVN_NO_ERROR;
79251881Speter
80251881Speter  /* Otherwise, we have to check the readability of each changed
81251881Speter     path, or at least enough to answer the question asked. */
82251881Speter  subpool = svn_pool_create(pool);
83251881Speter  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
84251881Speter    {
85299742Sdim      const char *key = apr_hash_this_key(hi);
86299742Sdim      svn_fs_path_change2_t *change = apr_hash_this_val(hi);
87251881Speter      svn_boolean_t readable;
88251881Speter
89251881Speter      svn_pool_clear(subpool);
90251881Speter
91251881Speter      SVN_ERR(authz_read_func(&readable, rev_root, key,
92251881Speter                              authz_read_baton, subpool));
93251881Speter      if (! readable)
94251881Speter        found_unreadable = TRUE;
95251881Speter      else
96251881Speter        found_readable = TRUE;
97251881Speter
98251881Speter      /* If we have at least one of each (readable/unreadable), we
99251881Speter         have our answer. */
100251881Speter      if (found_readable && found_unreadable)
101251881Speter        goto decision;
102251881Speter
103251881Speter      switch (change->change_kind)
104251881Speter        {
105251881Speter        case svn_fs_path_change_add:
106251881Speter        case svn_fs_path_change_replace:
107251881Speter          {
108251881Speter            const char *copyfrom_path;
109251881Speter            svn_revnum_t copyfrom_rev;
110251881Speter
111251881Speter            SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
112251881Speter                                       rev_root, key, subpool));
113251881Speter            if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
114251881Speter              {
115251881Speter                svn_fs_root_t *copyfrom_root;
116251881Speter                SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
117251881Speter                                             copyfrom_rev, subpool));
118251881Speter                SVN_ERR(authz_read_func(&readable,
119251881Speter                                        copyfrom_root, copyfrom_path,
120251881Speter                                        authz_read_baton, subpool));
121251881Speter                if (! readable)
122251881Speter                  found_unreadable = TRUE;
123251881Speter
124251881Speter                /* If we have at least one of each (readable/unreadable), we
125251881Speter                   have our answer. */
126251881Speter                if (found_readable && found_unreadable)
127251881Speter                  goto decision;
128251881Speter              }
129251881Speter          }
130251881Speter          break;
131251881Speter
132251881Speter        case svn_fs_path_change_delete:
133251881Speter        case svn_fs_path_change_modify:
134251881Speter        default:
135251881Speter          break;
136251881Speter        }
137251881Speter    }
138251881Speter
139251881Speter decision:
140251881Speter  svn_pool_destroy(subpool);
141251881Speter
142251881Speter  /* Either every changed path was unreadable... */
143251881Speter  if (! found_readable)
144251881Speter    *access_level = svn_repos_revision_access_none;
145251881Speter
146251881Speter  /* ... or some changed path was unreadable... */
147251881Speter  else if (found_unreadable)
148251881Speter    *access_level = svn_repos_revision_access_partial;
149251881Speter
150251881Speter  /* ... or every changed path was readable (the default). */
151251881Speter  return SVN_NO_ERROR;
152251881Speter}
153251881Speter
154251881Speter
155251881Speter/* Store as keys in CHANGED the paths of all node in ROOT that show a
156251881Speter * significant change.  "Significant" means that the text or
157251881Speter * properties of the node were changed, or that the node was added or
158251881Speter * deleted.
159251881Speter *
160251881Speter * The CHANGED hash set and its keys and values are allocated in POOL;
161251881Speter * keys are const char * paths and values are svn_log_changed_path_t.
162251881Speter *
163251881Speter * To prevent changes from being processed over and over again, the
164251881Speter * changed paths for ROOT may be passed in PREFETCHED_CHANGES.  If the
165251881Speter * latter is NULL, we will request the list inside this function.
166251881Speter *
167251881Speter * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
168251881Speter * AUTHZ_READ_BATON and FS) to check whether each changed-path (and
169251881Speter * copyfrom_path) is readable:
170251881Speter *
171299742Sdim *     - If absolutely every changed-path (and copyfrom_path) is
172299742Sdim *     readable, then return the full CHANGED hash, and set
173299742Sdim *     *ACCESS_LEVEL to svn_repos_revision_access_full.
174299742Sdim *
175251881Speter *     - If some paths are readable and some are not, then silently
176299742Sdim *     omit the unreadable paths from the CHANGED hash, and set
177299742Sdim *     *ACCESS_LEVEL to svn_repos_revision_access_partial.
178251881Speter *
179251881Speter *     - If absolutely every changed-path (and copyfrom_path) is
180299742Sdim *     unreadable, then return an empty CHANGED hash, and set
181299742Sdim *     *ACCESS_LEVEL to svn_repos_revision_access_none.  (This is
182299742Sdim *     to distinguish a revision which truly has no changed paths
183299742Sdim *     from a revision in which all paths are unreadable.)
184251881Speter */
185251881Speterstatic svn_error_t *
186299742Sdimdetect_changed(svn_repos_revision_access_level_t *access_level,
187299742Sdim               apr_hash_t **changed,
188251881Speter               svn_fs_root_t *root,
189251881Speter               svn_fs_t *fs,
190251881Speter               apr_hash_t *prefetched_changes,
191251881Speter               svn_repos_authz_func_t authz_read_func,
192251881Speter               void *authz_read_baton,
193251881Speter               apr_pool_t *pool)
194251881Speter{
195251881Speter  apr_hash_t *changes = prefetched_changes;
196251881Speter  apr_hash_index_t *hi;
197299742Sdim  apr_pool_t *iterpool;
198251881Speter  svn_boolean_t found_readable = FALSE;
199251881Speter  svn_boolean_t found_unreadable = FALSE;
200251881Speter
201299742Sdim  /* If we create the CHANGES hash ourselves, we can reuse it as the
202299742Sdim   * result hash as it contains the exact same keys - but with _all_
203299742Sdim   * values being replaced by structs of a different type. */
204251881Speter  if (changes == NULL)
205299742Sdim    {
206299742Sdim      SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
207251881Speter
208299742Sdim      /* If we are going to filter the results, we won't use the exact
209299742Sdim       * same keys but put them into a new hash. */
210299742Sdim      if (authz_read_func)
211299742Sdim        *changed = svn_hash__make(pool);
212299742Sdim      else
213299742Sdim        *changed = changes;
214299742Sdim    }
215299742Sdim  else
216299742Sdim    {
217299742Sdim      *changed = svn_hash__make(pool);
218299742Sdim    }
219299742Sdim
220251881Speter  if (apr_hash_count(changes) == 0)
221299742Sdim    {
222299742Sdim      /* No paths changed in this revision?  Uh, sure, I guess the
223299742Sdim         revision is readable, then.  */
224299742Sdim      *access_level = svn_repos_revision_access_full;
225299742Sdim      return SVN_NO_ERROR;
226299742Sdim    }
227251881Speter
228299742Sdim  iterpool = svn_pool_create(pool);
229251881Speter  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
230251881Speter    {
231251881Speter      /* NOTE:  Much of this loop is going to look quite similar to
232251881Speter         svn_repos_check_revision_access(), but we have to do more things
233251881Speter         here, so we'll live with the duplication. */
234299742Sdim      const char *path = apr_hash_this_key(hi);
235299742Sdim      apr_ssize_t path_len = apr_hash_this_key_len(hi);
236299742Sdim      svn_fs_path_change2_t *change = apr_hash_this_val(hi);
237251881Speter      char action;
238251881Speter      svn_log_changed_path2_t *item;
239251881Speter
240299742Sdim      svn_pool_clear(iterpool);
241251881Speter
242251881Speter      /* Skip path if unreadable. */
243251881Speter      if (authz_read_func)
244251881Speter        {
245251881Speter          svn_boolean_t readable;
246251881Speter          SVN_ERR(authz_read_func(&readable,
247251881Speter                                  root, path,
248299742Sdim                                  authz_read_baton, iterpool));
249251881Speter          if (! readable)
250251881Speter            {
251251881Speter              found_unreadable = TRUE;
252251881Speter              continue;
253251881Speter            }
254251881Speter        }
255251881Speter
256251881Speter      /* At least one changed-path was readable. */
257251881Speter      found_readable = TRUE;
258251881Speter
259251881Speter      switch (change->change_kind)
260251881Speter        {
261251881Speter        case svn_fs_path_change_reset:
262251881Speter          continue;
263251881Speter
264251881Speter        case svn_fs_path_change_add:
265251881Speter          action = 'A';
266251881Speter          break;
267251881Speter
268251881Speter        case svn_fs_path_change_replace:
269251881Speter          action = 'R';
270251881Speter          break;
271251881Speter
272251881Speter        case svn_fs_path_change_delete:
273251881Speter          action = 'D';
274251881Speter          break;
275251881Speter
276251881Speter        case svn_fs_path_change_modify:
277251881Speter        default:
278251881Speter          action = 'M';
279251881Speter          break;
280251881Speter        }
281251881Speter
282251881Speter      item = svn_log_changed_path2_create(pool);
283251881Speter      item->action = action;
284251881Speter      item->node_kind = change->node_kind;
285251881Speter      item->copyfrom_rev = SVN_INVALID_REVNUM;
286251881Speter      item->text_modified = change->text_mod ? svn_tristate_true
287251881Speter                                             : svn_tristate_false;
288251881Speter      item->props_modified = change->prop_mod ? svn_tristate_true
289251881Speter                                              : svn_tristate_false;
290251881Speter
291251881Speter      /* Pre-1.6 revision files don't store the change path kind, so fetch
292251881Speter         it manually. */
293251881Speter      if (item->node_kind == svn_node_unknown)
294251881Speter        {
295251881Speter          svn_fs_root_t *check_root = root;
296251881Speter          const char *check_path = path;
297251881Speter
298251881Speter          /* Deleted items don't exist so check earlier revision.  We
299251881Speter             know the parent must exist and could be a copy */
300251881Speter          if (change->change_kind == svn_fs_path_change_delete)
301251881Speter            {
302251881Speter              svn_fs_history_t *history;
303251881Speter              svn_revnum_t prev_rev;
304251881Speter              const char *parent_path, *name;
305251881Speter
306299742Sdim              svn_fspath__split(&parent_path, &name, path, iterpool);
307251881Speter
308299742Sdim              SVN_ERR(svn_fs_node_history2(&history, root, parent_path,
309299742Sdim                                           iterpool, iterpool));
310251881Speter
311251881Speter              /* Two calls because the first call returns the original
312251881Speter                 revision as the deleted child means it is 'interesting' */
313299742Sdim              SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
314299742Sdim                                           iterpool));
315299742Sdim              SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
316299742Sdim                                           iterpool));
317251881Speter
318251881Speter              SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev, history,
319299742Sdim                                              iterpool));
320299742Sdim              SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev, iterpool));
321299742Sdim              check_path = svn_fspath__join(parent_path, name, iterpool);
322251881Speter            }
323251881Speter
324251881Speter          SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path,
325299742Sdim                                    iterpool));
326251881Speter        }
327251881Speter
328251881Speter
329251881Speter      if ((action == 'A') || (action == 'R'))
330251881Speter        {
331251881Speter          const char *copyfrom_path = change->copyfrom_path;
332251881Speter          svn_revnum_t copyfrom_rev = change->copyfrom_rev;
333251881Speter
334251881Speter          /* the following is a potentially expensive operation since on FSFS
335251881Speter             we will follow the DAG from ROOT to PATH and that requires
336251881Speter             actually reading the directories along the way. */
337251881Speter          if (!change->copyfrom_known)
338299742Sdim            {
339299742Sdim              SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
340299742Sdim                                        root, path, iterpool));
341299742Sdim              copyfrom_path = apr_pstrdup(pool, copyfrom_path);
342299742Sdim            }
343251881Speter
344251881Speter          if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
345251881Speter            {
346251881Speter              svn_boolean_t readable = TRUE;
347251881Speter
348251881Speter              if (authz_read_func)
349251881Speter                {
350251881Speter                  svn_fs_root_t *copyfrom_root;
351251881Speter
352251881Speter                  SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
353299742Sdim                                               copyfrom_rev, iterpool));
354251881Speter                  SVN_ERR(authz_read_func(&readable,
355251881Speter                                          copyfrom_root, copyfrom_path,
356299742Sdim                                          authz_read_baton, iterpool));
357251881Speter                  if (! readable)
358251881Speter                    found_unreadable = TRUE;
359251881Speter                }
360251881Speter
361251881Speter              if (readable)
362251881Speter                {
363299742Sdim                  item->copyfrom_path = copyfrom_path;
364251881Speter                  item->copyfrom_rev = copyfrom_rev;
365251881Speter                }
366251881Speter            }
367251881Speter        }
368299742Sdim
369299742Sdim      apr_hash_set(*changed, path, path_len, item);
370251881Speter    }
371251881Speter
372299742Sdim  svn_pool_destroy(iterpool);
373251881Speter
374251881Speter  if (! found_readable)
375299742Sdim    {
376299742Sdim      /* Every changed-path was unreadable. */
377299742Sdim      *access_level = svn_repos_revision_access_none;
378299742Sdim    }
379299742Sdim  else if (found_unreadable)
380299742Sdim    {
381299742Sdim      /* At least one changed-path was unreadable. */
382299742Sdim      *access_level = svn_repos_revision_access_partial;
383299742Sdim    }
384299742Sdim  else
385299742Sdim    {
386299742Sdim      /* Every changed-path was readable. */
387299742Sdim      *access_level = svn_repos_revision_access_full;
388299742Sdim    }
389251881Speter
390251881Speter  return SVN_NO_ERROR;
391251881Speter}
392251881Speter
393251881Speter/* This is used by svn_repos_get_logs to keep track of multiple
394251881Speter * path history information while working through history.
395251881Speter *
396251881Speter * The two pools are swapped after each iteration through history because
397251881Speter * to get the next history requires the previous one.
398251881Speter */
399251881Speterstruct path_info
400251881Speter{
401251881Speter  svn_stringbuf_t *path;
402251881Speter  svn_revnum_t history_rev;
403251881Speter  svn_boolean_t done;
404251881Speter  svn_boolean_t first_time;
405251881Speter
406251881Speter  /* If possible, we like to keep open the history object for each path,
407251881Speter     since it avoids needed to open and close it many times as we walk
408251881Speter     backwards in time.  To do so we need two pools, so that we can clear
409251881Speter     one each time through.  If we're not holding the history open for
410251881Speter     this path then these three pointers will be NULL. */
411251881Speter  svn_fs_history_t *hist;
412251881Speter  apr_pool_t *newpool;
413251881Speter  apr_pool_t *oldpool;
414251881Speter};
415251881Speter
416251881Speter/* Advance to the next history for the path.
417251881Speter *
418251881Speter * If INFO->HIST is not NULL we do this using that existing history object,
419251881Speter * otherwise we open a new one.
420251881Speter *
421251881Speter * If no more history is available or the history revision is less
422251881Speter * (earlier) than START, or the history is not available due
423251881Speter * to authorization, then INFO->DONE is set to TRUE.
424251881Speter *
425251881Speter * A STRICT value of FALSE will indicate to follow history across copied
426251881Speter * paths.
427251881Speter *
428251881Speter * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
429251881Speter * AUTHZ_READ_BATON and FS) to check whether INFO->PATH is still readable if
430251881Speter * we do indeed find more history for the path.
431251881Speter */
432251881Speterstatic svn_error_t *
433251881Speterget_history(struct path_info *info,
434251881Speter            svn_fs_t *fs,
435251881Speter            svn_boolean_t strict,
436251881Speter            svn_repos_authz_func_t authz_read_func,
437251881Speter            void *authz_read_baton,
438251881Speter            svn_revnum_t start,
439299742Sdim            apr_pool_t *result_pool,
440299742Sdim            apr_pool_t *scratch_pool)
441251881Speter{
442251881Speter  svn_fs_root_t *history_root = NULL;
443251881Speter  svn_fs_history_t *hist;
444251881Speter  apr_pool_t *subpool;
445251881Speter  const char *path;
446251881Speter
447251881Speter  if (info->hist)
448251881Speter    {
449251881Speter      subpool = info->newpool;
450251881Speter
451299742Sdim      SVN_ERR(svn_fs_history_prev2(&info->hist, info->hist, ! strict,
452299742Sdim                                   subpool, scratch_pool));
453251881Speter
454251881Speter      hist = info->hist;
455251881Speter    }
456251881Speter  else
457251881Speter    {
458299742Sdim      subpool = svn_pool_create(result_pool);
459251881Speter
460251881Speter      /* Open the history located at the last rev we were at. */
461251881Speter      SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev,
462251881Speter                                   subpool));
463251881Speter
464299742Sdim      SVN_ERR(svn_fs_node_history2(&hist, history_root, info->path->data,
465299742Sdim                                   subpool, scratch_pool));
466251881Speter
467299742Sdim      SVN_ERR(svn_fs_history_prev2(&hist, hist, ! strict, subpool,
468299742Sdim                                   scratch_pool));
469251881Speter
470251881Speter      if (info->first_time)
471251881Speter        info->first_time = FALSE;
472251881Speter      else
473299742Sdim        SVN_ERR(svn_fs_history_prev2(&hist, hist, ! strict, subpool,
474299742Sdim                                     scratch_pool));
475251881Speter    }
476251881Speter
477251881Speter  if (! hist)
478251881Speter    {
479251881Speter      svn_pool_destroy(subpool);
480251881Speter      if (info->oldpool)
481251881Speter        svn_pool_destroy(info->oldpool);
482251881Speter      info->done = TRUE;
483251881Speter      return SVN_NO_ERROR;
484251881Speter    }
485251881Speter
486251881Speter  /* Fetch the location information for this history step. */
487251881Speter  SVN_ERR(svn_fs_history_location(&path, &info->history_rev,
488251881Speter                                  hist, subpool));
489251881Speter
490251881Speter  svn_stringbuf_set(info->path, path);
491251881Speter
492251881Speter  /* If this history item predates our START revision then
493251881Speter     don't fetch any more for this path. */
494251881Speter  if (info->history_rev < start)
495251881Speter    {
496251881Speter      svn_pool_destroy(subpool);
497251881Speter      if (info->oldpool)
498251881Speter        svn_pool_destroy(info->oldpool);
499251881Speter      info->done = TRUE;
500251881Speter      return SVN_NO_ERROR;
501251881Speter    }
502251881Speter
503251881Speter  /* Is the history item readable?  If not, done with path. */
504251881Speter  if (authz_read_func)
505251881Speter    {
506251881Speter      svn_boolean_t readable;
507251881Speter      SVN_ERR(svn_fs_revision_root(&history_root, fs,
508251881Speter                                   info->history_rev,
509299742Sdim                                   scratch_pool));
510251881Speter      SVN_ERR(authz_read_func(&readable, history_root,
511251881Speter                              info->path->data,
512251881Speter                              authz_read_baton,
513299742Sdim                              scratch_pool));
514251881Speter      if (! readable)
515251881Speter        info->done = TRUE;
516251881Speter    }
517251881Speter
518251881Speter  if (! info->hist)
519251881Speter    {
520251881Speter      svn_pool_destroy(subpool);
521251881Speter    }
522251881Speter  else
523251881Speter    {
524251881Speter      apr_pool_t *temppool = info->oldpool;
525251881Speter      info->oldpool = info->newpool;
526251881Speter      svn_pool_clear(temppool);
527251881Speter      info->newpool = temppool;
528251881Speter    }
529251881Speter
530251881Speter  return SVN_NO_ERROR;
531251881Speter}
532251881Speter
533251881Speter/* Set INFO->HIST to the next history for the path *if* there is history
534251881Speter * available and INFO->HISTORY_REV is equal to or greater than CURRENT.
535251881Speter *
536251881Speter * *CHANGED is set to TRUE if the path has history in the CURRENT revision,
537251881Speter * otherwise it is not touched.
538251881Speter *
539251881Speter * If we do need to get the next history revision for the path, call
540251881Speter * get_history to do it -- see it for details.
541251881Speter */
542251881Speterstatic svn_error_t *
543251881Spetercheck_history(svn_boolean_t *changed,
544251881Speter              struct path_info *info,
545251881Speter              svn_fs_t *fs,
546251881Speter              svn_revnum_t current,
547251881Speter              svn_boolean_t strict,
548251881Speter              svn_repos_authz_func_t authz_read_func,
549251881Speter              void *authz_read_baton,
550251881Speter              svn_revnum_t start,
551299742Sdim              apr_pool_t *result_pool,
552299742Sdim              apr_pool_t *scratch_pool)
553251881Speter{
554251881Speter  /* If we're already done with histories for this path,
555251881Speter     don't try to fetch any more. */
556251881Speter  if (info->done)
557251881Speter    return SVN_NO_ERROR;
558251881Speter
559251881Speter  /* If the last rev we got for this path is less than CURRENT,
560251881Speter     then just return and don't fetch history for this path.
561251881Speter     The caller will get to this rev eventually or else reach
562251881Speter     the limit. */
563251881Speter  if (info->history_rev < current)
564251881Speter    return SVN_NO_ERROR;
565251881Speter
566251881Speter  /* If the last rev we got for this path is equal to CURRENT
567251881Speter     then set *CHANGED to true and get the next history
568251881Speter     rev where this path was changed. */
569251881Speter  *changed = TRUE;
570251881Speter  return get_history(info, fs, strict, authz_read_func,
571299742Sdim                     authz_read_baton, start, result_pool, scratch_pool);
572251881Speter}
573251881Speter
574251881Speter/* Return the next interesting revision in our list of HISTORIES. */
575251881Speterstatic svn_revnum_t
576251881Speternext_history_rev(const apr_array_header_t *histories)
577251881Speter{
578251881Speter  svn_revnum_t next_rev = SVN_INVALID_REVNUM;
579251881Speter  int i;
580251881Speter
581251881Speter  for (i = 0; i < histories->nelts; ++i)
582251881Speter    {
583251881Speter      struct path_info *info = APR_ARRAY_IDX(histories, i,
584251881Speter                                             struct path_info *);
585251881Speter      if (info->done)
586251881Speter        continue;
587251881Speter      if (info->history_rev > next_rev)
588251881Speter        next_rev = info->history_rev;
589251881Speter    }
590251881Speter
591251881Speter  return next_rev;
592251881Speter}
593251881Speter
594251881Speter/* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to
595251881Speter   catalogs describing how mergeinfo values on paths (which are the
596299742Sdim   keys of those catalogs) were changed in REV.  If *PREFETCHED_CHANGES
597251881Speter   already contains the changed paths for REV, use that.  Otherwise,
598251881Speter   request that data and return it in *PREFETCHED_CHANGES. */
599251881Speter/* ### TODO: This would make a *great*, useful public function,
600251881Speter   ### svn_repos_fs_mergeinfo_changed()!  -- cmpilato  */
601251881Speterstatic svn_error_t *
602251881Speterfs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
603251881Speter                     svn_mergeinfo_catalog_t *added_mergeinfo_catalog,
604251881Speter                     apr_hash_t **prefetched_changes,
605251881Speter                     svn_fs_t *fs,
606251881Speter                     svn_revnum_t rev,
607251881Speter                     apr_pool_t *result_pool,
608251881Speter                     apr_pool_t *scratch_pool)
609251881Speter{
610251881Speter  svn_fs_root_t *root;
611251881Speter  apr_pool_t *iterpool;
612251881Speter  apr_hash_index_t *hi;
613299742Sdim  svn_boolean_t any_mergeinfo = FALSE;
614299742Sdim  svn_boolean_t any_copy = FALSE;
615251881Speter
616251881Speter  /* Initialize return variables. */
617251881Speter  *deleted_mergeinfo_catalog = svn_hash__make(result_pool);
618251881Speter  *added_mergeinfo_catalog = svn_hash__make(result_pool);
619251881Speter
620251881Speter  /* Revision 0 has no mergeinfo and no mergeinfo changes. */
621251881Speter  if (rev == 0)
622251881Speter    return SVN_NO_ERROR;
623251881Speter
624251881Speter  /* We're going to use the changed-paths information for REV to
625251881Speter     narrow down our search. */
626251881Speter  SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
627251881Speter  if (*prefetched_changes == NULL)
628251881Speter    SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool));
629251881Speter
630299742Sdim  /* Look for copies and (potential) mergeinfo changes.
631299742Sdim     We will use both flags to take shortcuts further down the road. */
632299742Sdim  for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
633299742Sdim       hi;
634299742Sdim       hi = apr_hash_next(hi))
635299742Sdim    {
636299742Sdim      svn_fs_path_change2_t *change = apr_hash_this_val(hi);
637299742Sdim
638299742Sdim      /* If there was a prop change and we are not positive that _no_
639299742Sdim         mergeinfo change happened, we must assume that it might have. */
640299742Sdim      if (change->mergeinfo_mod != svn_tristate_false && change->prop_mod)
641299742Sdim        any_mergeinfo = TRUE;
642299742Sdim
643299742Sdim      switch (change->change_kind)
644299742Sdim        {
645299742Sdim        case svn_fs_path_change_add:
646299742Sdim        case svn_fs_path_change_replace:
647299742Sdim          any_copy = TRUE;
648299742Sdim          break;
649299742Sdim
650299742Sdim        default:
651299742Sdim          break;
652299742Sdim        }
653299742Sdim    }
654299742Sdim
655299742Sdim  /* No potential mergeinfo changes?  We're done. */
656299742Sdim  if (! any_mergeinfo)
657251881Speter    return SVN_NO_ERROR;
658251881Speter
659251881Speter  /* Loop over changes, looking for anything that might carry an
660251881Speter     svn:mergeinfo change and is one of our paths of interest, or a
661251881Speter     child or [grand]parent directory thereof. */
662251881Speter  iterpool = svn_pool_create(scratch_pool);
663251881Speter  for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
664251881Speter       hi;
665251881Speter       hi = apr_hash_next(hi))
666251881Speter    {
667299742Sdim      const char *changed_path;
668299742Sdim      svn_fs_path_change2_t *change = apr_hash_this_val(hi);
669299742Sdim      const char *base_path = NULL;
670251881Speter      svn_revnum_t base_rev = SVN_INVALID_REVNUM;
671251881Speter      svn_fs_root_t *base_root = NULL;
672251881Speter      svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value;
673251881Speter
674299742Sdim      /* Cheap pre-checks that don't require memory allocation etc. */
675251881Speter
676299742Sdim      /* No mergeinfo change? -> nothing to do here. */
677299742Sdim      if (change->mergeinfo_mod == svn_tristate_false)
678299742Sdim        continue;
679251881Speter
680251881Speter      /* If there was no property change on this item, ignore it. */
681251881Speter      if (! change->prop_mod)
682251881Speter        continue;
683251881Speter
684299742Sdim      /* Begin actual processing */
685299742Sdim      changed_path = apr_hash_this_key(hi);
686299742Sdim      svn_pool_clear(iterpool);
687299742Sdim
688251881Speter      switch (change->change_kind)
689251881Speter        {
690251881Speter
691251881Speter        /* ### TODO: Can the add, replace, and modify cases be joined
692251881Speter           ### together to all use svn_repos__prev_location()?  The
693251881Speter           ### difference would be the fallback case (path/rev-1 for
694251881Speter           ### modifies, NULL otherwise).  -- cmpilato  */
695251881Speter
696251881Speter        /* If the path was merely modified, see if its previous
697251881Speter           location was affected by a copy which happened in this
698251881Speter           revision before assuming it holds the same path it did the
699251881Speter           previous revision. */
700251881Speter        case svn_fs_path_change_modify:
701251881Speter          {
702251881Speter            svn_revnum_t appeared_rev;
703251881Speter
704299742Sdim            /* If there were no copies in this revision, the path will have
705299742Sdim               existed in the previous rev.  Otherwise, we might just got
706299742Sdim               copied here and need to check for that eventuality. */
707299742Sdim            if (any_copy)
708299742Sdim              {
709299742Sdim                SVN_ERR(svn_repos__prev_location(&appeared_rev, &base_path,
710299742Sdim                                                 &base_rev, fs, rev,
711299742Sdim                                                 changed_path, iterpool));
712251881Speter
713299742Sdim                /* If this path isn't the result of a copy that occurred
714299742Sdim                   in this revision, we can find the previous version of
715299742Sdim                   it in REV - 1 at the same path. */
716299742Sdim                if (! (base_path && SVN_IS_VALID_REVNUM(base_rev)
717299742Sdim                      && (appeared_rev == rev)))
718299742Sdim                  {
719299742Sdim                    base_path = changed_path;
720299742Sdim                    base_rev = rev - 1;
721299742Sdim                  }
722299742Sdim              }
723299742Sdim            else
724251881Speter              {
725251881Speter                base_path = changed_path;
726251881Speter                base_rev = rev - 1;
727251881Speter              }
728251881Speter            break;
729251881Speter          }
730251881Speter
731299742Sdim        /* If the path was added or replaced, see if it was created via
732299742Sdim           copy.  If so, set BASE_REV/BASE_PATH to its previous location.
733299742Sdim           If not, there's no previous location to examine -- leave
734299742Sdim           BASE_REV/BASE_PATH = -1/NULL.  */
735299742Sdim        case svn_fs_path_change_add:
736299742Sdim        case svn_fs_path_change_replace:
737299742Sdim          {
738299742Sdim            if (change->copyfrom_known)
739299742Sdim              {
740299742Sdim                base_rev = change->copyfrom_rev;
741299742Sdim                base_path = change->copyfrom_path;
742299742Sdim              }
743299742Sdim            else
744299742Sdim              {
745299742Sdim                SVN_ERR(svn_fs_copied_from(&base_rev, &base_path,
746299742Sdim                                          root, changed_path, iterpool));
747299742Sdim              }
748299742Sdim            break;
749299742Sdim          }
750299742Sdim
751251881Speter        /* We don't care about any of the other cases. */
752251881Speter        case svn_fs_path_change_delete:
753251881Speter        case svn_fs_path_change_reset:
754251881Speter        default:
755251881Speter          continue;
756251881Speter        }
757251881Speter
758251881Speter      /* If there was a base location, fetch its mergeinfo property value. */
759251881Speter      if (base_path && SVN_IS_VALID_REVNUM(base_rev))
760251881Speter        {
761251881Speter          SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, iterpool));
762251881Speter          SVN_ERR(svn_fs_node_prop(&prev_mergeinfo_value, base_root, base_path,
763251881Speter                                   SVN_PROP_MERGEINFO, iterpool));
764251881Speter        }
765251881Speter
766251881Speter      /* Now fetch the current (as of REV) mergeinfo property value. */
767251881Speter      SVN_ERR(svn_fs_node_prop(&mergeinfo_value, root, changed_path,
768251881Speter                               SVN_PROP_MERGEINFO, iterpool));
769251881Speter
770251881Speter      /* No mergeinfo on either the new or previous location?  Just
771251881Speter         skip it.  (If there *was* a change, it would have been in
772251881Speter         inherited mergeinfo only, which should be picked up by the
773251881Speter         iteration of this loop that finds the parent paths that
774251881Speter         really got changed.)  */
775251881Speter      if (! (mergeinfo_value || prev_mergeinfo_value))
776251881Speter        continue;
777251881Speter
778299742Sdim      /* Mergeinfo on both sides but it did not change? Skip that too. */
779299742Sdim      if (   mergeinfo_value && prev_mergeinfo_value
780299742Sdim          && svn_string_compare(mergeinfo_value, prev_mergeinfo_value))
781299742Sdim        continue;
782299742Sdim
783251881Speter      /* If mergeinfo was explicitly added or removed on this path, we
784251881Speter         need to check to see if that was a real semantic change of
785251881Speter         meaning.  So, fill in the "missing" mergeinfo value with the
786251881Speter         inherited mergeinfo for that path/revision.  */
787251881Speter      if (prev_mergeinfo_value && (! mergeinfo_value))
788251881Speter        {
789251881Speter          svn_mergeinfo_t tmp_mergeinfo;
790251881Speter
791299742Sdim          SVN_ERR(svn_fs__get_mergeinfo_for_path(&tmp_mergeinfo,
792299742Sdim                                                 root, changed_path,
793299742Sdim                                                 svn_mergeinfo_inherited, TRUE,
794299742Sdim                                                 iterpool, iterpool));
795251881Speter          if (tmp_mergeinfo)
796251881Speter            SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_value,
797251881Speter                                            tmp_mergeinfo,
798251881Speter                                            iterpool));
799251881Speter        }
800251881Speter      else if (mergeinfo_value && (! prev_mergeinfo_value)
801251881Speter               && base_path && SVN_IS_VALID_REVNUM(base_rev))
802251881Speter        {
803251881Speter          svn_mergeinfo_t tmp_mergeinfo;
804251881Speter
805299742Sdim          SVN_ERR(svn_fs__get_mergeinfo_for_path(&tmp_mergeinfo,
806299742Sdim                                                 base_root, base_path,
807299742Sdim                                                 svn_mergeinfo_inherited, TRUE,
808299742Sdim                                                 iterpool, iterpool));
809251881Speter          if (tmp_mergeinfo)
810251881Speter            SVN_ERR(svn_mergeinfo_to_string(&prev_mergeinfo_value,
811251881Speter                                            tmp_mergeinfo,
812251881Speter                                            iterpool));
813251881Speter        }
814251881Speter
815299742Sdim      /* Old and new mergeinfo probably differ in some way (we already
816299742Sdim         checked for textual equality further up). Store the before and
817299742Sdim         after mergeinfo values in our return hashes.  They may still be
818299742Sdim         equal as manual intervention may have only changed the formatting
819299742Sdim         but not the relevant contents. */
820251881Speter        {
821251881Speter          svn_mergeinfo_t prev_mergeinfo = NULL, mergeinfo = NULL;
822251881Speter          svn_mergeinfo_t deleted, added;
823251881Speter          const char *hash_path;
824251881Speter
825251881Speter          if (mergeinfo_value)
826251881Speter            SVN_ERR(svn_mergeinfo_parse(&mergeinfo,
827251881Speter                                        mergeinfo_value->data, iterpool));
828251881Speter          if (prev_mergeinfo_value)
829251881Speter            SVN_ERR(svn_mergeinfo_parse(&prev_mergeinfo,
830251881Speter                                        prev_mergeinfo_value->data, iterpool));
831251881Speter          SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
832251881Speter                                      mergeinfo, FALSE, result_pool,
833251881Speter                                      iterpool));
834251881Speter
835251881Speter          /* Toss interesting stuff into our return catalogs. */
836251881Speter          hash_path = apr_pstrdup(result_pool, changed_path);
837251881Speter          svn_hash_sets(*deleted_mergeinfo_catalog, hash_path, deleted);
838251881Speter          svn_hash_sets(*added_mergeinfo_catalog, hash_path, added);
839251881Speter        }
840251881Speter    }
841251881Speter
842251881Speter  svn_pool_destroy(iterpool);
843251881Speter  return SVN_NO_ERROR;
844251881Speter}
845251881Speter
846251881Speter
847251881Speter/* Determine what (if any) mergeinfo for PATHS was modified in
848251881Speter   revision REV, returning the differences for added mergeinfo in
849251881Speter   *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO.
850299742Sdim   If *PREFETCHED_CHANGES already contains the changed paths for
851251881Speter   REV, use that.  Otherwise, request that data and return it in
852299742Sdim   *PREFETCHED_CHANGES. */
853251881Speterstatic svn_error_t *
854251881Speterget_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
855251881Speter                               svn_mergeinfo_t *deleted_mergeinfo,
856251881Speter                               apr_hash_t **prefetched_changes,
857251881Speter                               svn_fs_t *fs,
858251881Speter                               const apr_array_header_t *paths,
859251881Speter                               svn_revnum_t rev,
860251881Speter                               apr_pool_t *result_pool,
861251881Speter                               apr_pool_t *scratch_pool)
862251881Speter{
863251881Speter  svn_mergeinfo_catalog_t added_mergeinfo_catalog, deleted_mergeinfo_catalog;
864251881Speter  apr_hash_index_t *hi;
865251881Speter  svn_fs_root_t *root;
866251881Speter  apr_pool_t *iterpool;
867251881Speter  int i;
868251881Speter  svn_error_t *err;
869251881Speter
870251881Speter  /* Initialize return value. */
871251881Speter  *added_mergeinfo = svn_hash__make(result_pool);
872251881Speter  *deleted_mergeinfo = svn_hash__make(result_pool);
873251881Speter
874251881Speter  /* If we're asking about revision 0, there's no mergeinfo to be found. */
875251881Speter  if (rev == 0)
876251881Speter    return SVN_NO_ERROR;
877251881Speter
878251881Speter  /* No paths?  No mergeinfo. */
879251881Speter  if (! paths->nelts)
880251881Speter    return SVN_NO_ERROR;
881251881Speter
882251881Speter  /* Fetch the mergeinfo changes for REV. */
883251881Speter  err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog,
884251881Speter                             &added_mergeinfo_catalog,
885251881Speter                             prefetched_changes,
886299742Sdim                             fs, rev,
887299742Sdim                             scratch_pool, scratch_pool);
888251881Speter  if (err)
889251881Speter    {
890251881Speter      if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
891251881Speter        {
892251881Speter          /* Issue #3896: If invalid mergeinfo is encountered the
893251881Speter             best we can do is ignore it and act as if there were
894251881Speter             no mergeinfo modifications. */
895251881Speter          svn_error_clear(err);
896251881Speter          return SVN_NO_ERROR;
897251881Speter        }
898251881Speter      else
899251881Speter        {
900251881Speter          return svn_error_trace(err);
901251881Speter        }
902251881Speter    }
903251881Speter
904251881Speter  /* In most revisions, there will be no mergeinfo change at all. */
905251881Speter  if (   apr_hash_count(deleted_mergeinfo_catalog) == 0
906251881Speter      && apr_hash_count(added_mergeinfo_catalog) == 0)
907251881Speter    return SVN_NO_ERROR;
908299742Sdim
909299742Sdim  /* Create a work subpool and get a root for REV. */
910299742Sdim  SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
911299742Sdim
912251881Speter  /* Check our PATHS for any changes to their inherited mergeinfo.
913251881Speter     (We deal with changes to mergeinfo directly *on* the paths in the
914251881Speter     following loop.)  */
915251881Speter  iterpool = svn_pool_create(scratch_pool);
916251881Speter  for (i = 0; i < paths->nelts; i++)
917251881Speter    {
918251881Speter      const char *path = APR_ARRAY_IDX(paths, i, const char *);
919251881Speter      const char *prev_path;
920251881Speter      svn_revnum_t appeared_rev, prev_rev;
921251881Speter      svn_fs_root_t *prev_root;
922251881Speter      svn_mergeinfo_t prev_mergeinfo, mergeinfo, deleted, added,
923251881Speter        prev_inherited_mergeinfo, inherited_mergeinfo;
924251881Speter
925251881Speter      svn_pool_clear(iterpool);
926251881Speter
927251881Speter      /* If this path is represented in the changed-mergeinfo hashes,
928251881Speter         we'll deal with it in the loop below. */
929251881Speter      if (svn_hash_gets(deleted_mergeinfo_catalog, path))
930251881Speter        continue;
931251881Speter
932251881Speter      /* Figure out what path/rev to compare against.  Ignore
933251881Speter         not-found errors returned by the filesystem.  */
934251881Speter      err = svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
935251881Speter                                     fs, rev, path, iterpool);
936251881Speter      if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
937251881Speter                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))
938251881Speter        {
939251881Speter          svn_error_clear(err);
940251881Speter          err = SVN_NO_ERROR;
941251881Speter          continue;
942251881Speter        }
943251881Speter      SVN_ERR(err);
944251881Speter
945251881Speter      /* If this path isn't the result of a copy that occurred in this
946251881Speter         revision, we can find the previous version of it in REV - 1
947251881Speter         at the same path. */
948251881Speter      if (! (prev_path && SVN_IS_VALID_REVNUM(prev_rev)
949251881Speter             && (appeared_rev == rev)))
950251881Speter        {
951251881Speter          prev_path = path;
952251881Speter          prev_rev = rev - 1;
953251881Speter        }
954251881Speter
955251881Speter      /* Fetch the previous mergeinfo (including inherited stuff) for
956251881Speter         this path.  Ignore not-found errors returned by the
957251881Speter         filesystem or invalid mergeinfo (Issue #3896).*/
958251881Speter      SVN_ERR(svn_fs_revision_root(&prev_root, fs, prev_rev, iterpool));
959299742Sdim      err = svn_fs__get_mergeinfo_for_path(&prev_mergeinfo,
960299742Sdim                                           prev_root, prev_path,
961299742Sdim                                           svn_mergeinfo_inherited, TRUE,
962299742Sdim                                           iterpool, iterpool);
963251881Speter      if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
964251881Speter                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
965251881Speter                  err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
966251881Speter        {
967251881Speter          svn_error_clear(err);
968251881Speter          err = SVN_NO_ERROR;
969251881Speter          continue;
970251881Speter        }
971251881Speter      SVN_ERR(err);
972251881Speter
973251881Speter      /* Issue #4022 'svn log -g interprets change in inherited mergeinfo due
974251881Speter         to move as a merge': A copy where the source and destination inherit
975251881Speter         mergeinfo from the same parent means the inherited mergeinfo of the
976251881Speter         source and destination will differ, but this diffrence is not
977251881Speter         indicative of a merge unless the mergeinfo on the inherited parent
978251881Speter         has actually changed.
979251881Speter
980251881Speter         To check for this we must fetch the "raw" previous inherited
981251881Speter         mergeinfo and the "raw" mergeinfo @REV then compare these. */
982299742Sdim      SVN_ERR(svn_fs__get_mergeinfo_for_path(&prev_inherited_mergeinfo,
983299742Sdim                                             prev_root, prev_path,
984299742Sdim                                             svn_mergeinfo_nearest_ancestor,
985299742Sdim                                             FALSE, /* adjust_inherited_mergeinfo */
986299742Sdim                                             iterpool, iterpool));
987251881Speter
988251881Speter      /* Fetch the current mergeinfo (as of REV, and including
989251881Speter         inherited stuff) for this path. */
990299742Sdim      SVN_ERR(svn_fs__get_mergeinfo_for_path(&mergeinfo,
991299742Sdim                                             root, path,
992299742Sdim                                             svn_mergeinfo_inherited, TRUE,
993299742Sdim                                             iterpool, iterpool));
994251881Speter
995251881Speter      /* Issue #4022 again, fetch the raw inherited mergeinfo. */
996299742Sdim      SVN_ERR(svn_fs__get_mergeinfo_for_path(&inherited_mergeinfo,
997299742Sdim                                             root, path,
998299742Sdim                                             svn_mergeinfo_nearest_ancestor,
999299742Sdim                                             FALSE, /* adjust_inherited_mergeinfo */
1000299742Sdim                                             iterpool, iterpool));
1001251881Speter
1002251881Speter      if (!prev_mergeinfo && !mergeinfo)
1003251881Speter        continue;
1004251881Speter
1005251881Speter      /* Last bit of issue #4022 checking. */
1006251881Speter      if (prev_inherited_mergeinfo && inherited_mergeinfo)
1007251881Speter        {
1008251881Speter          svn_boolean_t inherits_same_mergeinfo;
1009251881Speter
1010251881Speter          SVN_ERR(svn_mergeinfo__equals(&inherits_same_mergeinfo,
1011251881Speter                                        prev_inherited_mergeinfo,
1012251881Speter                                        inherited_mergeinfo,
1013251881Speter                                        TRUE, iterpool));
1014251881Speter          /* If a copy rather than an actual merge brought about an
1015251881Speter             inherited mergeinfo change then we are finished. */
1016251881Speter          if (inherits_same_mergeinfo)
1017251881Speter            continue;
1018251881Speter        }
1019251881Speter      else
1020251881Speter        {
1021251881Speter          svn_boolean_t same_mergeinfo;
1022251881Speter          SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo,
1023251881Speter                                        prev_inherited_mergeinfo,
1024299742Sdim                                        NULL,
1025251881Speter                                        TRUE, iterpool));
1026251881Speter          if (same_mergeinfo)
1027251881Speter            continue;
1028251881Speter        }
1029251881Speter
1030251881Speter      /* Compare, constrast, and combine the results. */
1031251881Speter      SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
1032251881Speter                                  mergeinfo, FALSE, result_pool, iterpool));
1033251881Speter      SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, deleted,
1034251881Speter                                   result_pool, iterpool));
1035251881Speter      SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, added,
1036251881Speter                                   result_pool, iterpool));
1037251881Speter     }
1038251881Speter
1039251881Speter  /* Merge all the mergeinfos which are, or are children of, one of
1040251881Speter     our paths of interest into one giant delta mergeinfo.  */
1041251881Speter  for (hi = apr_hash_first(scratch_pool, added_mergeinfo_catalog);
1042251881Speter       hi; hi = apr_hash_next(hi))
1043251881Speter    {
1044299742Sdim      const char *changed_path = apr_hash_this_key(hi);
1045299742Sdim      apr_ssize_t klen = apr_hash_this_key_len(hi);
1046299742Sdim      svn_mergeinfo_t added = apr_hash_this_val(hi);
1047299742Sdim      svn_mergeinfo_t deleted;
1048251881Speter
1049251881Speter      for (i = 0; i < paths->nelts; i++)
1050251881Speter        {
1051251881Speter          const char *path = APR_ARRAY_IDX(paths, i, const char *);
1052251881Speter          if (! svn_fspath__skip_ancestor(path, changed_path))
1053251881Speter            continue;
1054251881Speter          svn_pool_clear(iterpool);
1055299742Sdim          deleted = apr_hash_get(deleted_mergeinfo_catalog, changed_path, klen);
1056251881Speter          SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo,
1057251881Speter                                       svn_mergeinfo_dup(deleted, result_pool),
1058251881Speter                                       result_pool, iterpool));
1059251881Speter          SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo,
1060251881Speter                                       svn_mergeinfo_dup(added, result_pool),
1061251881Speter                                       result_pool, iterpool));
1062251881Speter
1063251881Speter          break;
1064251881Speter        }
1065251881Speter    }
1066251881Speter
1067251881Speter  svn_pool_destroy(iterpool);
1068251881Speter  return SVN_NO_ERROR;
1069251881Speter}
1070251881Speter
1071251881Speter
1072251881Speter/* Fill LOG_ENTRY with history information in FS at REV. */
1073251881Speterstatic svn_error_t *
1074251881Speterfill_log_entry(svn_log_entry_t *log_entry,
1075251881Speter               svn_revnum_t rev,
1076251881Speter               svn_fs_t *fs,
1077251881Speter               apr_hash_t *prefetched_changes,
1078251881Speter               svn_boolean_t discover_changed_paths,
1079251881Speter               const apr_array_header_t *revprops,
1080251881Speter               svn_repos_authz_func_t authz_read_func,
1081251881Speter               void *authz_read_baton,
1082251881Speter               apr_pool_t *pool)
1083251881Speter{
1084251881Speter  apr_hash_t *r_props, *changed_paths = NULL;
1085251881Speter  svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
1086299742Sdim  svn_boolean_t want_revprops = !revprops || revprops->nelts;
1087251881Speter
1088251881Speter  /* Discover changed paths if the user requested them
1089251881Speter     or if we need to check that they are readable. */
1090251881Speter  if ((rev > 0)
1091251881Speter      && (authz_read_func || discover_changed_paths))
1092251881Speter    {
1093251881Speter      svn_fs_root_t *newroot;
1094299742Sdim      svn_repos_revision_access_level_t access_level;
1095251881Speter
1096251881Speter      SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
1097299742Sdim      SVN_ERR(detect_changed(&access_level, &changed_paths,
1098299742Sdim                             newroot, fs, prefetched_changes,
1099299742Sdim                             authz_read_func, authz_read_baton,
1100299742Sdim                             pool));
1101251881Speter
1102299742Sdim      if (access_level == svn_repos_revision_access_none)
1103251881Speter        {
1104251881Speter          /* All changed-paths are unreadable, so clear all fields. */
1105251881Speter          changed_paths = NULL;
1106251881Speter          get_revprops = FALSE;
1107251881Speter        }
1108299742Sdim      else if (access_level == svn_repos_revision_access_partial)
1109251881Speter        {
1110251881Speter          /* At least one changed-path was unreadable, so censor all
1111251881Speter             but author and date.  (The unreadable paths are already
1112251881Speter             missing from the hash.) */
1113251881Speter          censor_revprops = TRUE;
1114251881Speter        }
1115251881Speter
1116251881Speter      /* It may be the case that an authz func was passed in, but
1117251881Speter         the user still doesn't want to see any changed-paths. */
1118251881Speter      if (! discover_changed_paths)
1119251881Speter        changed_paths = NULL;
1120251881Speter    }
1121251881Speter
1122299742Sdim  if (get_revprops && want_revprops)
1123251881Speter    {
1124251881Speter      /* User is allowed to see at least some revprops. */
1125251881Speter      SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
1126251881Speter      if (revprops == NULL)
1127251881Speter        {
1128251881Speter          /* Requested all revprops... */
1129251881Speter          if (censor_revprops)
1130251881Speter            {
1131251881Speter              /* ... but we can only return author/date. */
1132251881Speter              log_entry->revprops = svn_hash__make(pool);
1133251881Speter              svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1134251881Speter                            svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR));
1135251881Speter              svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
1136251881Speter                            svn_hash_gets(r_props, SVN_PROP_REVISION_DATE));
1137251881Speter            }
1138251881Speter          else
1139251881Speter            /* ... so return all we got. */
1140251881Speter            log_entry->revprops = r_props;
1141251881Speter        }
1142251881Speter      else
1143251881Speter        {
1144299742Sdim          int i;
1145299742Sdim
1146251881Speter          /* Requested only some revprops... */
1147299742Sdim
1148299742Sdim          /* Make "svn:author" and "svn:date" available as svn_string_t
1149299742Sdim             for efficient comparison via svn_string_compare().  Note that
1150299742Sdim             we want static initialization here and must therefore emulate
1151299742Sdim             strlen(x) by sizeof(x)-1. */
1152299742Sdim          static const svn_string_t svn_prop_revision_author
1153299742Sdim            = {SVN_PROP_REVISION_AUTHOR, sizeof(SVN_PROP_REVISION_AUTHOR)-1};
1154299742Sdim          static const svn_string_t svn_prop_revision_date
1155299742Sdim            = {SVN_PROP_REVISION_DATE, sizeof(SVN_PROP_REVISION_DATE)-1};
1156299742Sdim
1157299742Sdim          /* often only the standard revprops got requested and delivered.
1158299742Sdim             In that case, we can simply pass the hash on. */
1159299742Sdim          if (revprops->nelts == apr_hash_count(r_props) && !censor_revprops)
1160251881Speter            {
1161299742Sdim              log_entry->revprops = r_props;
1162299742Sdim              for (i = 0; i < revprops->nelts; i++)
1163299742Sdim                {
1164299742Sdim                  const svn_string_t *name
1165299742Sdim                    = APR_ARRAY_IDX(revprops, i, const svn_string_t *);
1166299742Sdim                  if (!apr_hash_get(r_props, name->data, name->len))
1167299742Sdim                    {
1168299742Sdim                      /* hash does not match list of revprops we want */
1169299742Sdim                      log_entry->revprops = NULL;
1170299742Sdim                      break;
1171299742Sdim                    }
1172299742Sdim                }
1173251881Speter            }
1174299742Sdim
1175299742Sdim          /* slow, revprop-by-revprop filtering */
1176299742Sdim          if (log_entry->revprops == NULL)
1177299742Sdim            for (i = 0; i < revprops->nelts; i++)
1178299742Sdim              {
1179299742Sdim                const svn_string_t *name
1180299742Sdim                  = APR_ARRAY_IDX(revprops, i, const svn_string_t *);
1181299742Sdim                svn_string_t *value
1182299742Sdim                  = apr_hash_get(r_props, name->data, name->len);
1183299742Sdim                if (censor_revprops
1184299742Sdim                    && !svn_string_compare(name, &svn_prop_revision_author)
1185299742Sdim                    && !svn_string_compare(name, &svn_prop_revision_date))
1186299742Sdim                  /* ... but we can only return author/date. */
1187299742Sdim                  continue;
1188299742Sdim                if (log_entry->revprops == NULL)
1189299742Sdim                  log_entry->revprops = svn_hash__make(pool);
1190299742Sdim                apr_hash_set(log_entry->revprops, name->data, name->len, value);
1191299742Sdim              }
1192251881Speter        }
1193251881Speter    }
1194251881Speter
1195251881Speter  log_entry->changed_paths = changed_paths;
1196251881Speter  log_entry->changed_paths2 = changed_paths;
1197251881Speter  log_entry->revision = rev;
1198251881Speter
1199251881Speter  return SVN_NO_ERROR;
1200251881Speter}
1201251881Speter
1202251881Speter/* Send a log message for REV to RECEIVER with its RECEIVER_BATON.
1203251881Speter
1204251881Speter   FS is used with REV to fetch the interesting history information,
1205251881Speter   such as changed paths, revprops, etc.
1206251881Speter
1207251881Speter   The detect_changed function is used if either AUTHZ_READ_FUNC is
1208251881Speter   not NULL, or if DISCOVER_CHANGED_PATHS is TRUE.  See it for details.
1209251881Speter
1210251881Speter   If DESCENDING_ORDER is true, send child messages in descending order.
1211251881Speter
1212251881Speter   If REVPROPS is NULL, retrieve all revision properties; else, retrieve
1213251881Speter   only the revision properties named by the (const char *) array elements
1214251881Speter   (i.e. retrieve none if the array is empty).
1215251881Speter
1216251881Speter   LOG_TARGET_HISTORY_AS_MERGEINFO, HANDLING_MERGED_REVISION, and
1217299742Sdim   NESTED_MERGES are as per the arguments of the same name to DO_LOGS.
1218299742Sdim   If HANDLING_MERGED_REVISION is true and *all* changed paths within REV are
1219251881Speter   already represented in LOG_TARGET_HISTORY_AS_MERGEINFO, then don't send
1220251881Speter   the log message for REV.  If SUBTRACTIVE_MERGE is true, then REV was
1221251881Speter   reverse merged.
1222251881Speter
1223251881Speter   If HANDLING_MERGED_REVISIONS is FALSE then ignore NESTED_MERGES.  Otherwise
1224251881Speter   if NESTED_MERGES is not NULL and REV is contained in it, then don't send
1225251881Speter   the log for REV, otherwise send it normally and add REV to
1226251881Speter   NESTED_MERGES. */
1227251881Speterstatic svn_error_t *
1228251881Spetersend_log(svn_revnum_t rev,
1229251881Speter         svn_fs_t *fs,
1230251881Speter         apr_hash_t *prefetched_changes,
1231251881Speter         svn_mergeinfo_t log_target_history_as_mergeinfo,
1232299742Sdim         svn_bit_array__t *nested_merges,
1233251881Speter         svn_boolean_t discover_changed_paths,
1234251881Speter         svn_boolean_t subtractive_merge,
1235251881Speter         svn_boolean_t handling_merged_revision,
1236251881Speter         const apr_array_header_t *revprops,
1237251881Speter         svn_boolean_t has_children,
1238251881Speter         svn_log_entry_receiver_t receiver,
1239251881Speter         void *receiver_baton,
1240251881Speter         svn_repos_authz_func_t authz_read_func,
1241251881Speter         void *authz_read_baton,
1242251881Speter         apr_pool_t *pool)
1243251881Speter{
1244251881Speter  svn_log_entry_t *log_entry;
1245251881Speter  /* Assume we want to send the log for REV. */
1246251881Speter  svn_boolean_t found_rev_of_interest = TRUE;
1247251881Speter
1248251881Speter  log_entry = svn_log_entry_create(pool);
1249251881Speter  SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes,
1250251881Speter                         discover_changed_paths || handling_merged_revision,
1251299742Sdim                         revprops, authz_read_func, authz_read_baton, pool));
1252251881Speter  log_entry->has_children = has_children;
1253251881Speter  log_entry->subtractive_merge = subtractive_merge;
1254251881Speter
1255251881Speter  /* Is REV a merged revision that is already part of
1256251881Speter     LOG_TARGET_HISTORY_AS_MERGEINFO?  If so then there is no
1257251881Speter     need to send it, since it already was (or will be) sent. */
1258251881Speter  if (handling_merged_revision
1259251881Speter      && log_entry->changed_paths2
1260251881Speter      && log_target_history_as_mergeinfo
1261251881Speter      && apr_hash_count(log_target_history_as_mergeinfo))
1262251881Speter    {
1263251881Speter      apr_hash_index_t *hi;
1264299742Sdim      apr_pool_t *iterpool = svn_pool_create(pool);
1265251881Speter
1266251881Speter      /* REV was merged in, but it might already be part of the log target's
1267251881Speter         natural history, so change our starting assumption. */
1268251881Speter      found_rev_of_interest = FALSE;
1269251881Speter
1270251881Speter      /* Look at each changed path in REV. */
1271299742Sdim      for (hi = apr_hash_first(pool, log_entry->changed_paths2);
1272251881Speter           hi;
1273251881Speter           hi = apr_hash_next(hi))
1274251881Speter        {
1275251881Speter          svn_boolean_t path_is_in_history = FALSE;
1276299742Sdim          const char *changed_path = apr_hash_this_key(hi);
1277251881Speter          apr_hash_index_t *hi2;
1278251881Speter
1279251881Speter          /* Look at each path on the log target's mergeinfo. */
1280299742Sdim          for (hi2 = apr_hash_first(iterpool,
1281251881Speter                                    log_target_history_as_mergeinfo);
1282251881Speter               hi2;
1283251881Speter               hi2 = apr_hash_next(hi2))
1284251881Speter            {
1285299742Sdim              const char *mergeinfo_path = apr_hash_this_key(hi2);
1286299742Sdim              svn_rangelist_t *rangelist = apr_hash_this_val(hi2);
1287251881Speter
1288251881Speter              /* Check whether CHANGED_PATH at revision REV is a child of
1289251881Speter                 a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
1290251881Speter              if (svn_fspath__skip_ancestor(mergeinfo_path, changed_path))
1291251881Speter                {
1292251881Speter                  int i;
1293251881Speter
1294251881Speter                  for (i = 0; i < rangelist->nelts; i++)
1295251881Speter                    {
1296251881Speter                      svn_merge_range_t *range =
1297251881Speter                        APR_ARRAY_IDX(rangelist, i,
1298251881Speter                                      svn_merge_range_t *);
1299251881Speter                      if (rev > range->start && rev <= range->end)
1300251881Speter                        {
1301251881Speter                          path_is_in_history = TRUE;
1302251881Speter                          break;
1303251881Speter                        }
1304251881Speter                    }
1305251881Speter                }
1306251881Speter              if (path_is_in_history)
1307251881Speter                break;
1308251881Speter            }
1309299742Sdim          svn_pool_clear(iterpool);
1310251881Speter
1311251881Speter          if (!path_is_in_history)
1312251881Speter            {
1313251881Speter              /* If even one path in LOG_ENTRY->CHANGED_PATHS2 is not part of
1314251881Speter                 LOG_TARGET_HISTORY_AS_MERGEINFO, then we want to send the
1315251881Speter                 log for REV. */
1316251881Speter              found_rev_of_interest = TRUE;
1317251881Speter              break;
1318251881Speter            }
1319251881Speter        }
1320299742Sdim      svn_pool_destroy(iterpool);
1321251881Speter    }
1322251881Speter
1323251881Speter  /* If we only got changed paths the sake of detecting redundant merged
1324251881Speter     revisions, then be sure we don't send that info to the receiver. */
1325251881Speter  if (!discover_changed_paths && handling_merged_revision)
1326251881Speter    log_entry->changed_paths = log_entry->changed_paths2 = NULL;
1327251881Speter
1328251881Speter  /* Send the entry to the receiver, unless it is a redundant merged
1329251881Speter     revision. */
1330251881Speter  if (found_rev_of_interest)
1331251881Speter    {
1332299742Sdim      apr_pool_t *scratch_pool;
1333299742Sdim
1334251881Speter      /* Is REV a merged revision we've already sent? */
1335251881Speter      if (nested_merges && handling_merged_revision)
1336251881Speter        {
1337299742Sdim          if (svn_bit_array__get(nested_merges, rev))
1338251881Speter            {
1339251881Speter              /* We already sent REV. */
1340251881Speter              return SVN_NO_ERROR;
1341251881Speter            }
1342251881Speter          else
1343251881Speter            {
1344251881Speter              /* NESTED_REVS needs to last across all the send_log, do_logs,
1345251881Speter                 handle_merged_revisions() recursions, so use the pool it
1346251881Speter                 was created in at the top of the recursion. */
1347299742Sdim              svn_bit_array__set(nested_merges, rev, TRUE);
1348251881Speter            }
1349251881Speter        }
1350251881Speter
1351299742Sdim      /* Pass a scratch pool to ensure no temporary state stored
1352299742Sdim         by the receiver callback persists. */
1353299742Sdim      scratch_pool = svn_pool_create(pool);
1354299742Sdim      SVN_ERR(receiver(receiver_baton, log_entry, scratch_pool));
1355299742Sdim      svn_pool_destroy(scratch_pool);
1356251881Speter    }
1357299742Sdim
1358299742Sdim  return SVN_NO_ERROR;
1359251881Speter}
1360251881Speter
1361251881Speter/* This controls how many history objects we keep open.  For any targets
1362251881Speter   over this number we have to open and close their histories as needed,
1363251881Speter   which is CPU intensive, but keeps us from using an unbounded amount of
1364251881Speter   memory. */
1365251881Speter#define MAX_OPEN_HISTORIES 32
1366251881Speter
1367251881Speter/* Get the histories for PATHS, and store them in *HISTORIES.
1368251881Speter
1369251881Speter   If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1370251881Speter   repository locations as fatal -- just ignore them.  */
1371251881Speterstatic svn_error_t *
1372251881Speterget_path_histories(apr_array_header_t **histories,
1373251881Speter                   svn_fs_t *fs,
1374251881Speter                   const apr_array_header_t *paths,
1375251881Speter                   svn_revnum_t hist_start,
1376251881Speter                   svn_revnum_t hist_end,
1377251881Speter                   svn_boolean_t strict_node_history,
1378251881Speter                   svn_boolean_t ignore_missing_locations,
1379251881Speter                   svn_repos_authz_func_t authz_read_func,
1380251881Speter                   void *authz_read_baton,
1381251881Speter                   apr_pool_t *pool)
1382251881Speter{
1383251881Speter  svn_fs_root_t *root;
1384251881Speter  apr_pool_t *iterpool;
1385251881Speter  svn_error_t *err;
1386251881Speter  int i;
1387251881Speter
1388251881Speter  /* Create a history object for each path so we can walk through
1389251881Speter     them all at the same time until we have all changes or LIMIT
1390251881Speter     is reached.
1391251881Speter
1392251881Speter     There is some pool fun going on due to the fact that we have
1393251881Speter     to hold on to the old pool with the history before we can
1394251881Speter     get the next history.
1395251881Speter  */
1396251881Speter  *histories = apr_array_make(pool, paths->nelts,
1397251881Speter                              sizeof(struct path_info *));
1398251881Speter
1399251881Speter  SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool));
1400251881Speter
1401251881Speter  iterpool = svn_pool_create(pool);
1402251881Speter  for (i = 0; i < paths->nelts; i++)
1403251881Speter    {
1404251881Speter      const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
1405251881Speter      struct path_info *info = apr_palloc(pool,
1406251881Speter                                          sizeof(struct path_info));
1407299742Sdim      svn_pool_clear(iterpool);
1408251881Speter
1409251881Speter      if (authz_read_func)
1410251881Speter        {
1411251881Speter          svn_boolean_t readable;
1412251881Speter          SVN_ERR(authz_read_func(&readable, root, this_path,
1413251881Speter                                  authz_read_baton, iterpool));
1414251881Speter          if (! readable)
1415251881Speter            return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
1416251881Speter        }
1417251881Speter
1418251881Speter      info->path = svn_stringbuf_create(this_path, pool);
1419251881Speter      info->done = FALSE;
1420251881Speter      info->history_rev = hist_end;
1421251881Speter      info->first_time = TRUE;
1422251881Speter
1423251881Speter      if (i < MAX_OPEN_HISTORIES)
1424251881Speter        {
1425299742Sdim          err = svn_fs_node_history2(&info->hist, root, this_path, pool,
1426299742Sdim                                     iterpool);
1427251881Speter          if (err
1428251881Speter              && ignore_missing_locations
1429251881Speter              && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1430251881Speter                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1431251881Speter                  err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1432251881Speter            {
1433251881Speter              svn_error_clear(err);
1434251881Speter              continue;
1435251881Speter            }
1436251881Speter          SVN_ERR(err);
1437251881Speter          info->newpool = svn_pool_create(pool);
1438251881Speter          info->oldpool = svn_pool_create(pool);
1439251881Speter        }
1440251881Speter      else
1441251881Speter        {
1442251881Speter          info->hist = NULL;
1443251881Speter          info->oldpool = NULL;
1444251881Speter          info->newpool = NULL;
1445251881Speter        }
1446251881Speter
1447251881Speter      err = get_history(info, fs,
1448251881Speter                        strict_node_history,
1449251881Speter                        authz_read_func, authz_read_baton,
1450299742Sdim                        hist_start, pool, iterpool);
1451251881Speter      if (err
1452251881Speter          && ignore_missing_locations
1453251881Speter          && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1454251881Speter              err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1455251881Speter              err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1456251881Speter        {
1457251881Speter          svn_error_clear(err);
1458251881Speter          continue;
1459251881Speter        }
1460251881Speter      SVN_ERR(err);
1461251881Speter      APR_ARRAY_PUSH(*histories, struct path_info *) = info;
1462251881Speter    }
1463251881Speter  svn_pool_destroy(iterpool);
1464251881Speter
1465251881Speter  return SVN_NO_ERROR;
1466251881Speter}
1467251881Speter
1468251881Speter/* Remove and return the first item from ARR. */
1469251881Speterstatic void *
1470251881Speterarray_pop_front(apr_array_header_t *arr)
1471251881Speter{
1472251881Speter  void *item = arr->elts;
1473251881Speter
1474251881Speter  if (apr_is_empty_array(arr))
1475251881Speter    return NULL;
1476251881Speter
1477251881Speter  arr->elts += arr->elt_size;
1478251881Speter  arr->nelts -= 1;
1479251881Speter  arr->nalloc -= 1;
1480251881Speter  return item;
1481251881Speter}
1482251881Speter
1483251881Speter/* A struct which represents a single revision range, and the paths which
1484251881Speter   have mergeinfo in that range. */
1485251881Speterstruct path_list_range
1486251881Speter{
1487251881Speter  apr_array_header_t *paths;
1488251881Speter  svn_merge_range_t range;
1489251881Speter
1490251881Speter  /* Is RANGE the result of a reverse merge? */
1491251881Speter  svn_boolean_t reverse_merge;
1492251881Speter};
1493251881Speter
1494251881Speter/* A struct which represents "inverse mergeinfo", that is, instead of having
1495251881Speter   a path->revision_range_list mapping, which is the way mergeinfo is commonly
1496251881Speter   represented, this struct enables a revision_range_list,path tuple, where
1497251881Speter   the paths can be accessed by revision. */
1498251881Speterstruct rangelist_path
1499251881Speter{
1500251881Speter  svn_rangelist_t *rangelist;
1501251881Speter  const char *path;
1502251881Speter};
1503251881Speter
1504251881Speter/* Comparator function for combine_mergeinfo_path_lists().  Sorts
1505251881Speter   rangelist_path structs in increasing order based upon starting revision,
1506251881Speter   then ending revision of the first element in the rangelist.
1507251881Speter
1508251881Speter   This does not sort rangelists based upon subsequent elements, only the
1509251881Speter   first range.  We'll sort any subsequent ranges in the correct order
1510251881Speter   when they get bumped up to the front by removal of earlier ones, so we
1511251881Speter   don't really have to sort them here.  See combine_mergeinfo_path_lists()
1512251881Speter   for details. */
1513251881Speterstatic int
1514251881Spetercompare_rangelist_paths(const void *a, const void *b)
1515251881Speter{
1516251881Speter  struct rangelist_path *rpa = *((struct rangelist_path *const *) a);
1517251881Speter  struct rangelist_path *rpb = *((struct rangelist_path *const *) b);
1518251881Speter  svn_merge_range_t *mra = APR_ARRAY_IDX(rpa->rangelist, 0,
1519251881Speter                                         svn_merge_range_t *);
1520251881Speter  svn_merge_range_t *mrb = APR_ARRAY_IDX(rpb->rangelist, 0,
1521251881Speter                                         svn_merge_range_t *);
1522251881Speter
1523251881Speter  if (mra->start < mrb->start)
1524251881Speter    return -1;
1525251881Speter  if (mra->start > mrb->start)
1526251881Speter    return 1;
1527251881Speter  if (mra->end < mrb->end)
1528251881Speter    return -1;
1529251881Speter  if (mra->end > mrb->end)
1530251881Speter    return 1;
1531251881Speter
1532251881Speter  return 0;
1533251881Speter}
1534251881Speter
1535251881Speter/* From MERGEINFO, return in *COMBINED_LIST, allocated in POOL, a list of
1536251881Speter   'struct path_list_range's.  This list represents the rangelists in
1537251881Speter   MERGEINFO and each path which has mergeinfo in that range.
1538251881Speter   If REVERSE_MERGE is true, then MERGEINFO represents mergeinfo removed
1539251881Speter   as the result of a reverse merge. */
1540251881Speterstatic svn_error_t *
1541251881Spetercombine_mergeinfo_path_lists(apr_array_header_t **combined_list,
1542251881Speter                             svn_mergeinfo_t mergeinfo,
1543251881Speter                             svn_boolean_t reverse_merge,
1544251881Speter                             apr_pool_t *pool)
1545251881Speter{
1546251881Speter  apr_hash_index_t *hi;
1547251881Speter  apr_array_header_t *rangelist_paths;
1548251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
1549251881Speter
1550251881Speter  /* Create a list of (revision range, path) tuples from MERGEINFO. */
1551251881Speter  rangelist_paths = apr_array_make(subpool, apr_hash_count(mergeinfo),
1552251881Speter                                   sizeof(struct rangelist_path *));
1553251881Speter  for (hi = apr_hash_first(subpool, mergeinfo); hi;
1554251881Speter       hi = apr_hash_next(hi))
1555251881Speter    {
1556251881Speter      int i;
1557251881Speter      struct rangelist_path *rp = apr_palloc(subpool, sizeof(*rp));
1558299742Sdim
1559299742Sdim      rp->path = apr_hash_this_key(hi);
1560299742Sdim      rp->rangelist = apr_hash_this_val(hi);
1561251881Speter      APR_ARRAY_PUSH(rangelist_paths, struct rangelist_path *) = rp;
1562251881Speter
1563251881Speter      /* We need to make local copies of the rangelist, since we will be
1564251881Speter         modifying it, below. */
1565251881Speter      rp->rangelist = svn_rangelist_dup(rp->rangelist, subpool);
1566251881Speter
1567251881Speter      /* Make all of the rangelists inclusive, both start and end. */
1568251881Speter      for (i = 0; i < rp->rangelist->nelts; i++)
1569251881Speter        APR_ARRAY_IDX(rp->rangelist, i, svn_merge_range_t *)->start += 1;
1570251881Speter    }
1571251881Speter
1572251881Speter  /* Loop over the (revision range, path) tuples, chopping them into
1573251881Speter     (revision range, paths) tuples, and appending those to the output
1574251881Speter     list. */
1575251881Speter  if (! *combined_list)
1576251881Speter    *combined_list = apr_array_make(pool, 0, sizeof(struct path_list_range *));
1577251881Speter
1578251881Speter  while (rangelist_paths->nelts > 1)
1579251881Speter    {
1580251881Speter      svn_revnum_t youngest, next_youngest, tail, youngest_end;
1581251881Speter      struct path_list_range *plr;
1582251881Speter      struct rangelist_path *rp;
1583251881Speter      int num_revs;
1584251881Speter      int i;
1585251881Speter
1586251881Speter      /* First, sort the list such that the start revision of the first
1587251881Speter         revision arrays are sorted. */
1588299742Sdim      svn_sort__array(rangelist_paths, compare_rangelist_paths);
1589251881Speter
1590251881Speter      /* Next, find the number of revision ranges which start with the same
1591251881Speter         revision. */
1592251881Speter      rp = APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1593251881Speter      youngest =
1594251881Speter        APR_ARRAY_IDX(rp->rangelist, 0, struct svn_merge_range_t *)->start;
1595251881Speter      next_youngest = youngest;
1596251881Speter      for (num_revs = 1; next_youngest == youngest; num_revs++)
1597251881Speter        {
1598251881Speter          if (num_revs == rangelist_paths->nelts)
1599251881Speter            {
1600251881Speter              num_revs += 1;
1601251881Speter              break;
1602251881Speter            }
1603251881Speter          rp = APR_ARRAY_IDX(rangelist_paths, num_revs,
1604251881Speter                             struct rangelist_path *);
1605251881Speter          next_youngest = APR_ARRAY_IDX(rp->rangelist, 0,
1606251881Speter                                        struct svn_merge_range_t *)->start;
1607251881Speter        }
1608251881Speter      num_revs -= 1;
1609251881Speter
1610251881Speter      /* The start of the new range will be YOUNGEST, and we now find the end
1611251881Speter         of the new range, which should be either one less than the next
1612251881Speter         earliest start of a rangelist, or the end of the first rangelist. */
1613251881Speter      youngest_end =
1614251881Speter        APR_ARRAY_IDX(APR_ARRAY_IDX(rangelist_paths, 0,
1615251881Speter                                    struct rangelist_path *)->rangelist,
1616251881Speter                      0, svn_merge_range_t *)->end;
1617251881Speter      if ( (next_youngest == youngest) || (youngest_end < next_youngest) )
1618251881Speter        tail = youngest_end;
1619251881Speter      else
1620251881Speter        tail = next_youngest - 1;
1621251881Speter
1622251881Speter      /* Insert the (earliest, tail) tuple into the output list, along with
1623251881Speter         a list of paths which match it. */
1624251881Speter      plr = apr_palloc(pool, sizeof(*plr));
1625251881Speter      plr->reverse_merge = reverse_merge;
1626251881Speter      plr->range.start = youngest;
1627251881Speter      plr->range.end = tail;
1628251881Speter      plr->paths = apr_array_make(pool, num_revs, sizeof(const char *));
1629251881Speter      for (i = 0; i < num_revs; i++)
1630251881Speter        APR_ARRAY_PUSH(plr->paths, const char *) =
1631251881Speter          APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *)->path;
1632251881Speter      APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1633251881Speter
1634251881Speter      /* Now, check to see which (rangelist path) combinations we can remove,
1635251881Speter         and do so. */
1636251881Speter      for (i = 0; i < num_revs; i++)
1637251881Speter        {
1638251881Speter          svn_merge_range_t *range;
1639251881Speter          rp = APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *);
1640251881Speter          range = APR_ARRAY_IDX(rp->rangelist, 0, svn_merge_range_t *);
1641251881Speter
1642251881Speter          /* Set the start of the range to beyond the end of the range we
1643251881Speter             just built.  If the range is now "inverted", we can get pop it
1644251881Speter             off the list. */
1645251881Speter          range->start = tail + 1;
1646251881Speter          if (range->start > range->end)
1647251881Speter            {
1648251881Speter              if (rp->rangelist->nelts == 1)
1649251881Speter                {
1650251881Speter                  /* The range is the only on its list, so we should remove
1651251881Speter                     the entire rangelist_path, adjusting our loop control
1652251881Speter                     variables appropriately. */
1653251881Speter                  array_pop_front(rangelist_paths);
1654251881Speter                  i--;
1655251881Speter                  num_revs--;
1656251881Speter                }
1657251881Speter              else
1658251881Speter                {
1659251881Speter                  /* We have more than one range on the list, so just remove
1660251881Speter                     the first one. */
1661251881Speter                  array_pop_front(rp->rangelist);
1662251881Speter                }
1663251881Speter            }
1664251881Speter        }
1665251881Speter    }
1666251881Speter
1667251881Speter  /* Finally, add the last remaining (revision range, path) to the output
1668251881Speter     list. */
1669251881Speter  if (rangelist_paths->nelts > 0)
1670251881Speter    {
1671251881Speter      struct rangelist_path *first_rp =
1672251881Speter        APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1673251881Speter      while (first_rp->rangelist->nelts > 0)
1674251881Speter        {
1675251881Speter          struct path_list_range *plr = apr_palloc(pool, sizeof(*plr));
1676251881Speter
1677251881Speter          plr->reverse_merge = reverse_merge;
1678251881Speter          plr->paths = apr_array_make(pool, 1, sizeof(const char *));
1679251881Speter          APR_ARRAY_PUSH(plr->paths, const char *) = first_rp->path;
1680251881Speter          plr->range = *APR_ARRAY_IDX(first_rp->rangelist, 0,
1681251881Speter                                      svn_merge_range_t *);
1682251881Speter          array_pop_front(first_rp->rangelist);
1683251881Speter          APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1684251881Speter        }
1685251881Speter    }
1686251881Speter
1687251881Speter  svn_pool_destroy(subpool);
1688251881Speter
1689251881Speter  return SVN_NO_ERROR;
1690251881Speter}
1691251881Speter
1692251881Speter
1693251881Speter/* Pity that C is so ... linear. */
1694251881Speterstatic svn_error_t *
1695251881Speterdo_logs(svn_fs_t *fs,
1696251881Speter        const apr_array_header_t *paths,
1697251881Speter        svn_mergeinfo_t log_target_history_as_mergeinfo,
1698251881Speter        svn_mergeinfo_t processed,
1699299742Sdim        svn_bit_array__t *nested_merges,
1700251881Speter        svn_revnum_t hist_start,
1701251881Speter        svn_revnum_t hist_end,
1702251881Speter        int limit,
1703251881Speter        svn_boolean_t discover_changed_paths,
1704251881Speter        svn_boolean_t strict_node_history,
1705251881Speter        svn_boolean_t include_merged_revisions,
1706251881Speter        svn_boolean_t handling_merged_revisions,
1707251881Speter        svn_boolean_t subtractive_merge,
1708251881Speter        svn_boolean_t ignore_missing_locations,
1709251881Speter        const apr_array_header_t *revprops,
1710251881Speter        svn_boolean_t descending_order,
1711251881Speter        svn_log_entry_receiver_t receiver,
1712251881Speter        void *receiver_baton,
1713251881Speter        svn_repos_authz_func_t authz_read_func,
1714251881Speter        void *authz_read_baton,
1715251881Speter        apr_pool_t *pool);
1716251881Speter
1717251881Speter/* Comparator function for handle_merged_revisions().  Sorts path_list_range
1718251881Speter   structs in increasing order based on the struct's RANGE.START revision,
1719251881Speter   then RANGE.END revision. */
1720251881Speterstatic int
1721251881Spetercompare_path_list_range(const void *a, const void *b)
1722251881Speter{
1723251881Speter  struct path_list_range *plr_a = *((struct path_list_range *const *) a);
1724251881Speter  struct path_list_range *plr_b = *((struct path_list_range *const *) b);
1725251881Speter
1726251881Speter  if (plr_a->range.start < plr_b->range.start)
1727251881Speter    return -1;
1728251881Speter  if (plr_a->range.start > plr_b->range.start)
1729251881Speter    return 1;
1730251881Speter  if (plr_a->range.end < plr_b->range.end)
1731251881Speter    return -1;
1732251881Speter  if (plr_a->range.end > plr_b->range.end)
1733251881Speter    return 1;
1734251881Speter
1735251881Speter  return 0;
1736251881Speter}
1737251881Speter
1738251881Speter/* Examine the ADDED_MERGEINFO and DELETED_MERGEINFO for revision REV in FS
1739251881Speter   (as collected by examining paths of interest to a log operation), and
1740251881Speter   determine which revisions to report as having been merged or reverse-merged
1741251881Speter   via the commit resulting in REV.
1742251881Speter
1743251881Speter   Silently ignore some failures to find the revisions mentioned in the
1744251881Speter   added/deleted mergeinfos, as might happen if there is invalid mergeinfo.
1745251881Speter
1746251881Speter   Other parameters are as described by do_logs(), around which this
1747251881Speter   is a recursion wrapper. */
1748251881Speterstatic svn_error_t *
1749251881Speterhandle_merged_revisions(svn_revnum_t rev,
1750251881Speter                        svn_fs_t *fs,
1751251881Speter                        svn_mergeinfo_t log_target_history_as_mergeinfo,
1752299742Sdim                        svn_bit_array__t *nested_merges,
1753251881Speter                        svn_mergeinfo_t processed,
1754251881Speter                        svn_mergeinfo_t added_mergeinfo,
1755251881Speter                        svn_mergeinfo_t deleted_mergeinfo,
1756251881Speter                        svn_boolean_t discover_changed_paths,
1757251881Speter                        svn_boolean_t strict_node_history,
1758251881Speter                        const apr_array_header_t *revprops,
1759251881Speter                        svn_log_entry_receiver_t receiver,
1760251881Speter                        void *receiver_baton,
1761251881Speter                        svn_repos_authz_func_t authz_read_func,
1762251881Speter                        void *authz_read_baton,
1763251881Speter                        apr_pool_t *pool)
1764251881Speter{
1765251881Speter  apr_array_header_t *combined_list = NULL;
1766251881Speter  svn_log_entry_t *empty_log_entry;
1767251881Speter  apr_pool_t *iterpool;
1768251881Speter  int i;
1769251881Speter
1770251881Speter  if (apr_hash_count(added_mergeinfo) == 0
1771251881Speter      && apr_hash_count(deleted_mergeinfo) == 0)
1772251881Speter    return SVN_NO_ERROR;
1773251881Speter
1774251881Speter  if (apr_hash_count(added_mergeinfo))
1775251881Speter    SVN_ERR(combine_mergeinfo_path_lists(&combined_list, added_mergeinfo,
1776251881Speter                                          FALSE, pool));
1777251881Speter
1778251881Speter  if (apr_hash_count(deleted_mergeinfo))
1779251881Speter    SVN_ERR(combine_mergeinfo_path_lists(&combined_list, deleted_mergeinfo,
1780251881Speter                                          TRUE, pool));
1781251881Speter
1782251881Speter  SVN_ERR_ASSERT(combined_list != NULL);
1783299742Sdim  svn_sort__array(combined_list, compare_path_list_range);
1784251881Speter
1785251881Speter  /* Because the combined_lists are ordered youngest to oldest,
1786251881Speter     iterate over them in reverse. */
1787251881Speter  iterpool = svn_pool_create(pool);
1788251881Speter  for (i = combined_list->nelts - 1; i >= 0; i--)
1789251881Speter    {
1790251881Speter      struct path_list_range *pl_range
1791251881Speter        = APR_ARRAY_IDX(combined_list, i, struct path_list_range *);
1792251881Speter
1793251881Speter      svn_pool_clear(iterpool);
1794251881Speter      SVN_ERR(do_logs(fs, pl_range->paths, log_target_history_as_mergeinfo,
1795251881Speter                      processed, nested_merges,
1796251881Speter                      pl_range->range.start, pl_range->range.end, 0,
1797251881Speter                      discover_changed_paths, strict_node_history,
1798251881Speter                      TRUE, pl_range->reverse_merge, TRUE, TRUE,
1799251881Speter                      revprops, TRUE, receiver, receiver_baton,
1800251881Speter                      authz_read_func, authz_read_baton, iterpool));
1801251881Speter    }
1802251881Speter  svn_pool_destroy(iterpool);
1803251881Speter
1804251881Speter  /* Send the empty revision.  */
1805251881Speter  empty_log_entry = svn_log_entry_create(pool);
1806251881Speter  empty_log_entry->revision = SVN_INVALID_REVNUM;
1807251881Speter  return (*receiver)(receiver_baton, empty_log_entry, pool);
1808251881Speter}
1809251881Speter
1810251881Speter/* This is used by do_logs to differentiate between forward and
1811251881Speter   reverse merges. */
1812251881Speterstruct added_deleted_mergeinfo
1813251881Speter{
1814251881Speter  svn_mergeinfo_t added_mergeinfo;
1815251881Speter  svn_mergeinfo_t deleted_mergeinfo;
1816251881Speter};
1817251881Speter
1818251881Speter/* Reduce the search range PATHS, HIST_START, HIST_END by removing
1819251881Speter   parts already covered by PROCESSED.  If reduction is possible
1820251881Speter   elements may be removed from PATHS and *START_REDUCED and
1821251881Speter   *END_REDUCED may be set to a narrower range. */
1822251881Speterstatic svn_error_t *
1823251881Speterreduce_search(apr_array_header_t *paths,
1824251881Speter              svn_revnum_t *hist_start,
1825251881Speter              svn_revnum_t *hist_end,
1826251881Speter              svn_mergeinfo_t processed,
1827251881Speter              apr_pool_t *scratch_pool)
1828251881Speter{
1829251881Speter  /* We add 1 to end to compensate for store_search */
1830251881Speter  svn_revnum_t start = *hist_start <= *hist_end ? *hist_start : *hist_end;
1831251881Speter  svn_revnum_t end = *hist_start <= *hist_end ? *hist_end + 1 : *hist_start + 1;
1832251881Speter  int i;
1833251881Speter
1834251881Speter  for (i = 0; i < paths->nelts; ++i)
1835251881Speter    {
1836251881Speter      const char *path = APR_ARRAY_IDX(paths, i, const char *);
1837251881Speter      svn_rangelist_t *ranges = svn_hash_gets(processed, path);
1838251881Speter      int j;
1839251881Speter
1840251881Speter      if (!ranges)
1841251881Speter        continue;
1842251881Speter
1843251881Speter      /* ranges is ordered, could we use some sort of binary search
1844251881Speter         rather than iterating? */
1845251881Speter      for (j = 0; j < ranges->nelts; ++j)
1846251881Speter        {
1847251881Speter          svn_merge_range_t *range = APR_ARRAY_IDX(ranges, j,
1848251881Speter                                                   svn_merge_range_t *);
1849251881Speter          if (range->start <= start && range->end >= end)
1850251881Speter            {
1851251881Speter              for (j = i; j < paths->nelts - 1; ++j)
1852251881Speter                APR_ARRAY_IDX(paths, j, const char *)
1853251881Speter                  = APR_ARRAY_IDX(paths, j + 1, const char *);
1854251881Speter
1855251881Speter              --paths->nelts;
1856251881Speter              --i;
1857251881Speter              break;
1858251881Speter            }
1859251881Speter
1860251881Speter          /* If there is only one path then we also check for a
1861251881Speter             partial overlap rather than the full overlap above, and
1862251881Speter             reduce the [hist_start, hist_end] range rather than
1863251881Speter             dropping the path. */
1864251881Speter          if (paths->nelts == 1)
1865251881Speter            {
1866251881Speter              if (range->start <= start && range->end > start)
1867251881Speter                {
1868251881Speter                  if (start == *hist_start)
1869251881Speter                    *hist_start = range->end - 1;
1870251881Speter                  else
1871251881Speter                    *hist_end = range->end - 1;
1872251881Speter                  break;
1873251881Speter                }
1874251881Speter              if (range->start < end && range->end >= end)
1875251881Speter                {
1876251881Speter                  if (start == *hist_start)
1877251881Speter                    *hist_end = range->start;
1878251881Speter                  else
1879251881Speter                    *hist_start = range->start;
1880251881Speter                  break;
1881251881Speter                }
1882251881Speter            }
1883251881Speter        }
1884251881Speter    }
1885251881Speter
1886251881Speter  return SVN_NO_ERROR;
1887251881Speter}
1888251881Speter
1889251881Speter/* Extend PROCESSED to cover PATHS from HIST_START to HIST_END */
1890251881Speterstatic svn_error_t *
1891251881Speterstore_search(svn_mergeinfo_t processed,
1892251881Speter             const apr_array_header_t *paths,
1893251881Speter             svn_revnum_t hist_start,
1894251881Speter             svn_revnum_t hist_end,
1895251881Speter             apr_pool_t *scratch_pool)
1896251881Speter{
1897251881Speter  /* We add 1 to end so that we can use the mergeinfo API to handle
1898251881Speter     singe revisions where HIST_START is equal to HIST_END. */
1899251881Speter  svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end;
1900251881Speter  svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1;
1901251881Speter  svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool);
1902251881Speter  apr_pool_t *processed_pool = apr_hash_pool_get(processed);
1903251881Speter  int i;
1904251881Speter
1905251881Speter  for (i = 0; i < paths->nelts; ++i)
1906251881Speter    {
1907251881Speter      const char *path = APR_ARRAY_IDX(paths, i, const char *);
1908251881Speter      svn_rangelist_t *ranges = apr_array_make(processed_pool, 1,
1909251881Speter                                               sizeof(svn_merge_range_t*));
1910251881Speter      svn_merge_range_t *range = apr_palloc(processed_pool,
1911251881Speter                                            sizeof(svn_merge_range_t));
1912251881Speter
1913251881Speter      range->start = start;
1914251881Speter      range->end = end;
1915251881Speter      range->inheritable = TRUE;
1916251881Speter      APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range;
1917251881Speter      svn_hash_sets(mergeinfo, apr_pstrdup(processed_pool, path), ranges);
1918251881Speter    }
1919251881Speter  SVN_ERR(svn_mergeinfo_merge2(processed, mergeinfo,
1920251881Speter                               apr_hash_pool_get(processed), scratch_pool));
1921251881Speter
1922251881Speter  return SVN_NO_ERROR;
1923251881Speter}
1924251881Speter
1925251881Speter/* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke
1926251881Speter   RECEIVER with RECEIVER_BATON on them.  If DESCENDING_ORDER is TRUE, send
1927251881Speter   the logs back as we find them, else buffer the logs and send them back
1928251881Speter   in youngest->oldest order.
1929251881Speter
1930251881Speter   If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1931251881Speter   repository locations as fatal -- just ignore them.
1932251881Speter
1933251881Speter   If LOG_TARGET_HISTORY_AS_MERGEINFO is not NULL then it contains mergeinfo
1934251881Speter   representing the history of PATHS between HIST_START and HIST_END.
1935251881Speter
1936251881Speter   If HANDLING_MERGED_REVISIONS is TRUE then this is a recursive call for
1937251881Speter   merged revisions, see INCLUDE_MERGED_REVISIONS argument to
1938251881Speter   svn_repos_get_logs4().  If SUBTRACTIVE_MERGE is true, then this is a
1939251881Speter   recursive call for reverse merged revisions.
1940251881Speter
1941251881Speter   If NESTED_MERGES is not NULL then it is a hash of revisions (svn_revnum_t *
1942251881Speter   mapped to svn_revnum_t *) for logs that were previously sent.  On the first
1943251881Speter   call to do_logs it should always be NULL.  If INCLUDE_MERGED_REVISIONS is
1944251881Speter   TRUE, then NESTED_MERGES will be created on the first call to do_logs,
1945251881Speter   allocated in POOL.  It is then shared across
1946251881Speter   do_logs()/send_logs()/handle_merge_revisions() recursions, see also the
1947251881Speter   argument of the same name in send_logs().
1948251881Speter
1949251881Speter   PROCESSED is a mergeinfo hash that represents the paths and
1950251881Speter   revisions that have already been searched.  Allocated like
1951251881Speter   NESTED_MERGES above.
1952251881Speter
1953251881Speter   All other parameters are the same as svn_repos_get_logs4().
1954251881Speter */
1955251881Speterstatic svn_error_t *
1956251881Speterdo_logs(svn_fs_t *fs,
1957251881Speter        const apr_array_header_t *paths,
1958251881Speter        svn_mergeinfo_t log_target_history_as_mergeinfo,
1959251881Speter        svn_mergeinfo_t processed,
1960299742Sdim        svn_bit_array__t *nested_merges,
1961251881Speter        svn_revnum_t hist_start,
1962251881Speter        svn_revnum_t hist_end,
1963251881Speter        int limit,
1964251881Speter        svn_boolean_t discover_changed_paths,
1965251881Speter        svn_boolean_t strict_node_history,
1966251881Speter        svn_boolean_t include_merged_revisions,
1967251881Speter        svn_boolean_t subtractive_merge,
1968251881Speter        svn_boolean_t handling_merged_revisions,
1969251881Speter        svn_boolean_t ignore_missing_locations,
1970251881Speter        const apr_array_header_t *revprops,
1971251881Speter        svn_boolean_t descending_order,
1972251881Speter        svn_log_entry_receiver_t receiver,
1973251881Speter        void *receiver_baton,
1974251881Speter        svn_repos_authz_func_t authz_read_func,
1975251881Speter        void *authz_read_baton,
1976251881Speter        apr_pool_t *pool)
1977251881Speter{
1978299742Sdim  apr_pool_t *iterpool, *iterpool2;
1979251881Speter  apr_pool_t *subpool = NULL;
1980251881Speter  apr_array_header_t *revs = NULL;
1981251881Speter  apr_hash_t *rev_mergeinfo = NULL;
1982251881Speter  svn_revnum_t current;
1983251881Speter  apr_array_header_t *histories;
1984251881Speter  svn_boolean_t any_histories_left = TRUE;
1985251881Speter  int send_count = 0;
1986251881Speter  int i;
1987251881Speter
1988251881Speter  if (processed)
1989251881Speter    {
1990251881Speter      /* Casting away const. This only happens on recursive calls when
1991251881Speter         it is known to be safe because we allocated paths. */
1992251881Speter      SVN_ERR(reduce_search((apr_array_header_t *)paths, &hist_start, &hist_end,
1993251881Speter                            processed, pool));
1994251881Speter    }
1995251881Speter
1996251881Speter  if (!paths->nelts)
1997251881Speter    return SVN_NO_ERROR;
1998251881Speter
1999251881Speter  if (processed)
2000251881Speter    SVN_ERR(store_search(processed, paths, hist_start, hist_end, pool));
2001251881Speter
2002251881Speter  /* We have a list of paths and a revision range.  But we don't care
2003251881Speter     about all the revisions in the range -- only the ones in which
2004251881Speter     one of our paths was changed.  So let's go figure out which
2005251881Speter     revisions contain real changes to at least one of our paths.  */
2006251881Speter  SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end,
2007251881Speter                             strict_node_history, ignore_missing_locations,
2008251881Speter                             authz_read_func, authz_read_baton, pool));
2009251881Speter
2010251881Speter  /* Loop through all the revisions in the range and add any
2011251881Speter     where a path was changed to the array, or if they wanted
2012251881Speter     history in reverse order just send it to them right away. */
2013251881Speter  iterpool = svn_pool_create(pool);
2014299742Sdim  iterpool2 = svn_pool_create(pool);
2015251881Speter  for (current = hist_end;
2016251881Speter       any_histories_left;
2017251881Speter       current = next_history_rev(histories))
2018251881Speter    {
2019251881Speter      svn_boolean_t changed = FALSE;
2020251881Speter      any_histories_left = FALSE;
2021251881Speter      svn_pool_clear(iterpool);
2022251881Speter
2023251881Speter      for (i = 0; i < histories->nelts; i++)
2024251881Speter        {
2025251881Speter          struct path_info *info = APR_ARRAY_IDX(histories, i,
2026251881Speter                                                 struct path_info *);
2027251881Speter
2028299742Sdim          svn_pool_clear(iterpool2);
2029299742Sdim
2030251881Speter          /* Check history for this path in current rev. */
2031251881Speter          SVN_ERR(check_history(&changed, info, fs, current,
2032251881Speter                                strict_node_history, authz_read_func,
2033299742Sdim                                authz_read_baton, hist_start, pool,
2034299742Sdim                                iterpool2));
2035251881Speter          if (! info->done)
2036251881Speter            any_histories_left = TRUE;
2037251881Speter        }
2038251881Speter
2039299742Sdim      svn_pool_clear(iterpool2);
2040299742Sdim
2041251881Speter      /* If any of the paths changed in this rev then add or send it. */
2042251881Speter      if (changed)
2043251881Speter        {
2044251881Speter          svn_mergeinfo_t added_mergeinfo = NULL;
2045251881Speter          svn_mergeinfo_t deleted_mergeinfo = NULL;
2046251881Speter          svn_boolean_t has_children = FALSE;
2047251881Speter          apr_hash_t *changes = NULL;
2048251881Speter
2049251881Speter          /* If we're including merged revisions, we need to calculate
2050251881Speter             the mergeinfo deltas committed in this revision to our
2051251881Speter             various paths. */
2052251881Speter          if (include_merged_revisions)
2053251881Speter            {
2054251881Speter              apr_array_header_t *cur_paths =
2055251881Speter                apr_array_make(iterpool, paths->nelts, sizeof(const char *));
2056251881Speter
2057251881Speter              /* Get the current paths of our history objects so we can
2058251881Speter                 query mergeinfo. */
2059251881Speter              /* ### TODO: Should this be ignoring depleted history items? */
2060251881Speter              for (i = 0; i < histories->nelts; i++)
2061251881Speter                {
2062251881Speter                  struct path_info *info = APR_ARRAY_IDX(histories, i,
2063251881Speter                                                         struct path_info *);
2064251881Speter                  APR_ARRAY_PUSH(cur_paths, const char *) = info->path->data;
2065251881Speter                }
2066251881Speter              SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo,
2067251881Speter                                                     &deleted_mergeinfo,
2068251881Speter                                                     &changes,
2069251881Speter                                                     fs, cur_paths,
2070299742Sdim                                                     current,
2071299742Sdim                                                     iterpool, iterpool));
2072251881Speter              has_children = (apr_hash_count(added_mergeinfo) > 0
2073251881Speter                              || apr_hash_count(deleted_mergeinfo) > 0);
2074251881Speter            }
2075251881Speter
2076251881Speter          /* If our caller wants logs in descending order, we can send
2077251881Speter             'em now (because that's the order we're crawling history
2078251881Speter             in anyway). */
2079251881Speter          if (descending_order)
2080251881Speter            {
2081251881Speter              SVN_ERR(send_log(current, fs, changes,
2082251881Speter                               log_target_history_as_mergeinfo, nested_merges,
2083251881Speter                               discover_changed_paths,
2084251881Speter                               subtractive_merge, handling_merged_revisions,
2085251881Speter                               revprops, has_children,
2086251881Speter                               receiver, receiver_baton,
2087251881Speter                               authz_read_func, authz_read_baton, iterpool));
2088251881Speter
2089251881Speter              if (has_children) /* Implies include_merged_revisions == TRUE */
2090251881Speter                {
2091251881Speter                  if (!nested_merges)
2092251881Speter                    {
2093251881Speter                      /* We're at the start of the recursion stack, create a
2094251881Speter                         single hash to be shared across all of the merged
2095251881Speter                         recursions so we can track and squelch duplicates. */
2096251881Speter                      subpool = svn_pool_create(pool);
2097299742Sdim                      nested_merges = svn_bit_array__create(hist_end, subpool);
2098251881Speter                      processed = svn_hash__make(subpool);
2099251881Speter                    }
2100251881Speter
2101251881Speter                  SVN_ERR(handle_merged_revisions(
2102251881Speter                    current, fs,
2103251881Speter                    log_target_history_as_mergeinfo, nested_merges,
2104251881Speter                    processed,
2105251881Speter                    added_mergeinfo, deleted_mergeinfo,
2106251881Speter                    discover_changed_paths,
2107251881Speter                    strict_node_history,
2108251881Speter                    revprops,
2109251881Speter                    receiver, receiver_baton,
2110251881Speter                    authz_read_func,
2111251881Speter                    authz_read_baton,
2112251881Speter                    iterpool));
2113251881Speter                }
2114251881Speter              if (limit && ++send_count >= limit)
2115251881Speter                break;
2116251881Speter            }
2117251881Speter          /* Otherwise, the caller wanted logs in ascending order, so
2118251881Speter             we have to buffer up a list of revs and (if doing
2119251881Speter             mergeinfo) a hash of related mergeinfo deltas, and
2120251881Speter             process them later. */
2121251881Speter          else
2122251881Speter            {
2123251881Speter              if (! revs)
2124251881Speter                revs = apr_array_make(pool, 64, sizeof(svn_revnum_t));
2125251881Speter              APR_ARRAY_PUSH(revs, svn_revnum_t) = current;
2126251881Speter
2127251881Speter              if (added_mergeinfo || deleted_mergeinfo)
2128251881Speter                {
2129299742Sdim                  svn_revnum_t *cur_rev =
2130299742Sdim                    apr_pmemdup(pool, &current, sizeof(*cur_rev));
2131251881Speter                  struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2132251881Speter                    apr_palloc(pool, sizeof(*add_and_del_mergeinfo));
2133251881Speter
2134299742Sdim                  /* If we have added or deleted mergeinfo, both are non-null */
2135299742Sdim                  SVN_ERR_ASSERT(added_mergeinfo && deleted_mergeinfo);
2136299742Sdim                  add_and_del_mergeinfo->added_mergeinfo =
2137299742Sdim                    svn_mergeinfo_dup(added_mergeinfo, pool);
2138299742Sdim                  add_and_del_mergeinfo->deleted_mergeinfo =
2139299742Sdim                    svn_mergeinfo_dup(deleted_mergeinfo, pool);
2140251881Speter
2141251881Speter                  if (! rev_mergeinfo)
2142251881Speter                    rev_mergeinfo = svn_hash__make(pool);
2143251881Speter                  apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev),
2144251881Speter                               add_and_del_mergeinfo);
2145251881Speter                }
2146251881Speter            }
2147251881Speter        }
2148251881Speter    }
2149299742Sdim  svn_pool_destroy(iterpool2);
2150251881Speter  svn_pool_destroy(iterpool);
2151251881Speter
2152251881Speter  if (subpool)
2153251881Speter    {
2154251881Speter      nested_merges = NULL;
2155251881Speter      svn_pool_destroy(subpool);
2156251881Speter    }
2157251881Speter
2158251881Speter  if (revs)
2159251881Speter    {
2160251881Speter      /* Work loop for processing the revisions we found since they wanted
2161251881Speter         history in forward order. */
2162251881Speter      iterpool = svn_pool_create(pool);
2163251881Speter      for (i = 0; i < revs->nelts; ++i)
2164251881Speter        {
2165251881Speter          svn_mergeinfo_t added_mergeinfo;
2166251881Speter          svn_mergeinfo_t deleted_mergeinfo;
2167251881Speter          svn_boolean_t has_children = FALSE;
2168251881Speter
2169251881Speter          svn_pool_clear(iterpool);
2170251881Speter          current = APR_ARRAY_IDX(revs, revs->nelts - i - 1, svn_revnum_t);
2171251881Speter
2172251881Speter          /* If we've got a hash of revision mergeinfo (which can only
2173251881Speter             happen if INCLUDE_MERGED_REVISIONS was set), we check to
2174251881Speter             see if this revision is one which merged in other
2175251881Speter             revisions we need to handle recursively. */
2176251881Speter          if (rev_mergeinfo)
2177251881Speter            {
2178251881Speter              struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2179251881Speter                apr_hash_get(rev_mergeinfo, &current, sizeof(svn_revnum_t));
2180251881Speter              added_mergeinfo = add_and_del_mergeinfo->added_mergeinfo;
2181251881Speter              deleted_mergeinfo = add_and_del_mergeinfo->deleted_mergeinfo;
2182251881Speter              has_children = (apr_hash_count(added_mergeinfo) > 0
2183251881Speter                              || apr_hash_count(deleted_mergeinfo) > 0);
2184251881Speter            }
2185251881Speter
2186251881Speter          SVN_ERR(send_log(current, fs, NULL,
2187251881Speter                           log_target_history_as_mergeinfo, nested_merges,
2188251881Speter                           discover_changed_paths, subtractive_merge,
2189299742Sdim                           handling_merged_revisions,
2190299742Sdim                           revprops, has_children,
2191251881Speter                           receiver, receiver_baton, authz_read_func,
2192251881Speter                           authz_read_baton, iterpool));
2193251881Speter          if (has_children)
2194251881Speter            {
2195251881Speter              if (!nested_merges)
2196251881Speter                {
2197251881Speter                  subpool = svn_pool_create(pool);
2198299742Sdim                  nested_merges = svn_bit_array__create(current, subpool);
2199251881Speter                }
2200251881Speter
2201251881Speter              SVN_ERR(handle_merged_revisions(current, fs,
2202251881Speter                                              log_target_history_as_mergeinfo,
2203251881Speter                                              nested_merges,
2204251881Speter                                              processed,
2205251881Speter                                              added_mergeinfo,
2206251881Speter                                              deleted_mergeinfo,
2207251881Speter                                              discover_changed_paths,
2208299742Sdim                                              strict_node_history,
2209299742Sdim                                              revprops,
2210251881Speter                                              receiver, receiver_baton,
2211251881Speter                                              authz_read_func,
2212251881Speter                                              authz_read_baton,
2213251881Speter                                              iterpool));
2214251881Speter            }
2215251881Speter          if (limit && i + 1 >= limit)
2216251881Speter            break;
2217251881Speter        }
2218251881Speter      svn_pool_destroy(iterpool);
2219251881Speter    }
2220251881Speter
2221251881Speter  return SVN_NO_ERROR;
2222251881Speter}
2223251881Speter
2224251881Speterstruct location_segment_baton
2225251881Speter{
2226251881Speter  apr_array_header_t *history_segments;
2227251881Speter  apr_pool_t *pool;
2228251881Speter};
2229251881Speter
2230251881Speter/* svn_location_segment_receiver_t implementation for svn_repos_get_logs4. */
2231251881Speterstatic svn_error_t *
2232251881Speterlocation_segment_receiver(svn_location_segment_t *segment,
2233251881Speter                          void *baton,
2234251881Speter                          apr_pool_t *pool)
2235251881Speter{
2236251881Speter  struct location_segment_baton *b = baton;
2237251881Speter
2238251881Speter  APR_ARRAY_PUSH(b->history_segments, svn_location_segment_t *) =
2239251881Speter    svn_location_segment_dup(segment, b->pool);
2240251881Speter
2241251881Speter  return SVN_NO_ERROR;
2242251881Speter}
2243251881Speter
2244251881Speter
2245251881Speter/* Populate *PATHS_HISTORY_MERGEINFO with mergeinfo representing the combined
2246251881Speter   history of each path in PATHS between START_REV and END_REV in REPOS's
2247251881Speter   filesystem.  START_REV and END_REV must be valid revisions.  RESULT_POOL
2248251881Speter   is used to allocate *PATHS_HISTORY_MERGEINFO, SCRATCH_POOL is used for all
2249251881Speter   other (temporary) allocations.  Other parameters are the same as
2250251881Speter   svn_repos_get_logs4(). */
2251251881Speterstatic svn_error_t *
2252251881Speterget_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo,
2253251881Speter                               svn_repos_t *repos,
2254251881Speter                               const apr_array_header_t *paths,
2255251881Speter                               svn_revnum_t start_rev,
2256251881Speter                               svn_revnum_t end_rev,
2257251881Speter                               svn_repos_authz_func_t authz_read_func,
2258251881Speter                               void *authz_read_baton,
2259251881Speter                               apr_pool_t *result_pool,
2260251881Speter                               apr_pool_t *scratch_pool)
2261251881Speter{
2262251881Speter  int i;
2263251881Speter  svn_mergeinfo_t path_history_mergeinfo;
2264251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2265251881Speter
2266251881Speter  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_rev));
2267251881Speter  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_rev));
2268251881Speter
2269251881Speter  /* Ensure START_REV is the youngest revision, as required by
2270251881Speter     svn_repos_node_location_segments, for which this is an iterative
2271251881Speter     wrapper. */
2272251881Speter  if (start_rev < end_rev)
2273251881Speter    {
2274251881Speter      svn_revnum_t tmp_rev = start_rev;
2275251881Speter      start_rev = end_rev;
2276251881Speter      end_rev = tmp_rev;
2277251881Speter    }
2278251881Speter
2279251881Speter  *paths_history_mergeinfo = svn_hash__make(result_pool);
2280251881Speter
2281251881Speter  for (i = 0; i < paths->nelts; i++)
2282251881Speter    {
2283251881Speter      const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
2284251881Speter      struct location_segment_baton loc_seg_baton;
2285251881Speter
2286251881Speter      svn_pool_clear(iterpool);
2287251881Speter      loc_seg_baton.pool = scratch_pool;
2288251881Speter      loc_seg_baton.history_segments =
2289251881Speter        apr_array_make(iterpool, 4, sizeof(svn_location_segment_t *));
2290251881Speter
2291251881Speter      SVN_ERR(svn_repos_node_location_segments(repos, this_path, start_rev,
2292251881Speter                                               start_rev, end_rev,
2293251881Speter                                               location_segment_receiver,
2294251881Speter                                               &loc_seg_baton,
2295251881Speter                                               authz_read_func,
2296251881Speter                                               authz_read_baton,
2297251881Speter                                               iterpool));
2298251881Speter
2299251881Speter      SVN_ERR(svn_mergeinfo__mergeinfo_from_segments(
2300251881Speter        &path_history_mergeinfo, loc_seg_baton.history_segments, iterpool));
2301251881Speter      SVN_ERR(svn_mergeinfo_merge2(*paths_history_mergeinfo,
2302251881Speter                                   svn_mergeinfo_dup(path_history_mergeinfo,
2303251881Speter                                                     result_pool),
2304251881Speter                                   result_pool, iterpool));
2305251881Speter    }
2306251881Speter  svn_pool_destroy(iterpool);
2307251881Speter  return SVN_NO_ERROR;
2308251881Speter}
2309251881Speter
2310251881Spetersvn_error_t *
2311251881Spetersvn_repos_get_logs4(svn_repos_t *repos,
2312251881Speter                    const apr_array_header_t *paths,
2313251881Speter                    svn_revnum_t start,
2314251881Speter                    svn_revnum_t end,
2315251881Speter                    int limit,
2316251881Speter                    svn_boolean_t discover_changed_paths,
2317251881Speter                    svn_boolean_t strict_node_history,
2318251881Speter                    svn_boolean_t include_merged_revisions,
2319251881Speter                    const apr_array_header_t *revprops,
2320251881Speter                    svn_repos_authz_func_t authz_read_func,
2321251881Speter                    void *authz_read_baton,
2322251881Speter                    svn_log_entry_receiver_t receiver,
2323251881Speter                    void *receiver_baton,
2324251881Speter                    apr_pool_t *pool)
2325251881Speter{
2326251881Speter  svn_revnum_t head = SVN_INVALID_REVNUM;
2327251881Speter  svn_fs_t *fs = repos->fs;
2328251881Speter  svn_boolean_t descending_order;
2329251881Speter  svn_mergeinfo_t paths_history_mergeinfo = NULL;
2330251881Speter
2331299742Sdim  if (revprops)
2332299742Sdim    {
2333299742Sdim      int i;
2334299742Sdim      apr_array_header_t *new_revprops
2335299742Sdim        = apr_array_make(pool, revprops->nelts, sizeof(svn_string_t *));
2336299742Sdim
2337299742Sdim      for (i = 0; i < revprops->nelts; ++i)
2338299742Sdim        APR_ARRAY_PUSH(new_revprops, svn_string_t *)
2339299742Sdim          = svn_string_create(APR_ARRAY_IDX(revprops, i, const char *), pool);
2340299742Sdim
2341299742Sdim      revprops = new_revprops;
2342299742Sdim    }
2343299742Sdim
2344251881Speter  /* Setup log range. */
2345251881Speter  SVN_ERR(svn_fs_youngest_rev(&head, fs, pool));
2346251881Speter
2347251881Speter  if (! SVN_IS_VALID_REVNUM(start))
2348251881Speter    start = head;
2349251881Speter
2350251881Speter  if (! SVN_IS_VALID_REVNUM(end))
2351251881Speter    end = head;
2352251881Speter
2353251881Speter  /* Check that revisions are sane before ever invoking receiver. */
2354251881Speter  if (start > head)
2355251881Speter    return svn_error_createf
2356251881Speter      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2357251881Speter       _("No such revision %ld"), start);
2358251881Speter  if (end > head)
2359251881Speter    return svn_error_createf
2360251881Speter      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2361251881Speter       _("No such revision %ld"), end);
2362251881Speter
2363251881Speter  /* Ensure a youngest-to-oldest revision crawl ordering using our
2364251881Speter     (possibly sanitized) range values. */
2365251881Speter  descending_order = start >= end;
2366251881Speter  if (descending_order)
2367251881Speter    {
2368251881Speter      svn_revnum_t tmp_rev = start;
2369251881Speter      start = end;
2370251881Speter      end = tmp_rev;
2371251881Speter    }
2372251881Speter
2373251881Speter  if (! paths)
2374251881Speter    paths = apr_array_make(pool, 0, sizeof(const char *));
2375251881Speter
2376251881Speter  /* If we're not including merged revisions, and we were given no
2377251881Speter     paths or a single empty (or "/") path, then we can bypass a bunch
2378251881Speter     of complexity because we already know in which revisions the root
2379251881Speter     directory was changed -- all of them.  */
2380251881Speter  if ((! include_merged_revisions)
2381251881Speter      && ((! paths->nelts)
2382251881Speter          || ((paths->nelts == 1)
2383251881Speter              && (svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *))
2384251881Speter                  || (strcmp(APR_ARRAY_IDX(paths, 0, const char *),
2385251881Speter                             "/") == 0)))))
2386251881Speter    {
2387251881Speter      apr_uint64_t send_count = 0;
2388251881Speter      int i;
2389251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
2390251881Speter
2391251881Speter      /* If we are provided an authz callback function, use it to
2392251881Speter         verify that the user has read access to the root path in the
2393251881Speter         first of our revisions.
2394251881Speter
2395251881Speter         ### FIXME:  Strictly speaking, we should be checking this
2396251881Speter         ### access in every revision along the line.  But currently,
2397251881Speter         ### there are no known authz implementations which concern
2398251881Speter         ### themselves with per-revision access.  */
2399251881Speter      if (authz_read_func)
2400251881Speter        {
2401251881Speter          svn_boolean_t readable;
2402251881Speter          svn_fs_root_t *rev_root;
2403251881Speter
2404251881Speter          SVN_ERR(svn_fs_revision_root(&rev_root, fs,
2405251881Speter                                       descending_order ? end : start, pool));
2406251881Speter          SVN_ERR(authz_read_func(&readable, rev_root, "",
2407251881Speter                                  authz_read_baton, pool));
2408251881Speter          if (! readable)
2409251881Speter            return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
2410251881Speter        }
2411251881Speter
2412251881Speter      send_count = end - start + 1;
2413299742Sdim      if (limit > 0 && send_count > limit)
2414251881Speter        send_count = limit;
2415251881Speter      for (i = 0; i < send_count; ++i)
2416251881Speter        {
2417251881Speter          svn_revnum_t rev;
2418251881Speter
2419251881Speter          svn_pool_clear(iterpool);
2420251881Speter
2421251881Speter          if (descending_order)
2422251881Speter            rev = end - i;
2423251881Speter          else
2424251881Speter            rev = start + i;
2425251881Speter          SVN_ERR(send_log(rev, fs, NULL, NULL, NULL,
2426251881Speter                           discover_changed_paths, FALSE,
2427299742Sdim                           FALSE, revprops, FALSE, receiver, receiver_baton,
2428299742Sdim                           authz_read_func, authz_read_baton, iterpool));
2429251881Speter        }
2430251881Speter      svn_pool_destroy(iterpool);
2431251881Speter
2432251881Speter      return SVN_NO_ERROR;
2433251881Speter    }
2434251881Speter
2435251881Speter  /* If we are including merged revisions, then create mergeinfo that
2436251881Speter     represents all of PATHS' history between START and END.  We will use
2437251881Speter     this later to squelch duplicate log revisions that might exist in
2438251881Speter     both natural history and merged-in history.  See
2439251881Speter     http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */
2440251881Speter  if (include_merged_revisions)
2441251881Speter    {
2442251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
2443251881Speter
2444251881Speter      SVN_ERR(get_paths_history_as_mergeinfo(&paths_history_mergeinfo,
2445251881Speter                                             repos, paths, start, end,
2446251881Speter                                             authz_read_func,
2447251881Speter                                             authz_read_baton,
2448251881Speter                                             pool, subpool));
2449251881Speter      svn_pool_destroy(subpool);
2450251881Speter    }
2451251881Speter
2452251881Speter  return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL, start, end,
2453251881Speter                 limit, discover_changed_paths, strict_node_history,
2454299742Sdim                 include_merged_revisions, FALSE, FALSE, FALSE,
2455299742Sdim                 revprops, descending_order, receiver, receiver_baton,
2456251881Speter                 authz_read_func, authz_read_baton, pool);
2457251881Speter}
2458