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"
42251881Speter#include "private/svn_mergeinfo_private.h"
43251881Speter#include "private/svn_subr_private.h"
44251881Speter
45251881Speter
46251881Speter
47251881Spetersvn_error_t *
48251881Spetersvn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
49251881Speter                                svn_repos_t *repos,
50251881Speter                                svn_revnum_t revision,
51251881Speter                                svn_repos_authz_func_t authz_read_func,
52251881Speter                                void *authz_read_baton,
53251881Speter                                apr_pool_t *pool)
54251881Speter{
55251881Speter  svn_fs_t *fs = svn_repos_fs(repos);
56251881Speter  svn_fs_root_t *rev_root;
57251881Speter  apr_hash_t *changes;
58251881Speter  apr_hash_index_t *hi;
59251881Speter  svn_boolean_t found_readable = FALSE;
60251881Speter  svn_boolean_t found_unreadable = FALSE;
61251881Speter  apr_pool_t *subpool;
62251881Speter
63251881Speter  /* By default, we'll grant full read access to REVISION. */
64251881Speter  *access_level = svn_repos_revision_access_full;
65251881Speter
66251881Speter  /* No auth-checking function?  We're done. */
67251881Speter  if (! authz_read_func)
68251881Speter    return SVN_NO_ERROR;
69251881Speter
70251881Speter  /* Fetch the changes associated with REVISION. */
71251881Speter  SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool));
72251881Speter  SVN_ERR(svn_fs_paths_changed2(&changes, rev_root, pool));
73251881Speter
74251881Speter  /* No changed paths?  We're done. */
75251881Speter  if (apr_hash_count(changes) == 0)
76251881Speter    return SVN_NO_ERROR;
77251881Speter
78251881Speter  /* Otherwise, we have to check the readability of each changed
79251881Speter     path, or at least enough to answer the question asked. */
80251881Speter  subpool = svn_pool_create(pool);
81251881Speter  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
82251881Speter    {
83251881Speter      const void *key;
84251881Speter      void *val;
85251881Speter      svn_fs_path_change2_t *change;
86251881Speter      svn_boolean_t readable;
87251881Speter
88251881Speter      svn_pool_clear(subpool);
89251881Speter      apr_hash_this(hi, &key, NULL, &val);
90251881Speter      change = val;
91251881Speter
92251881Speter      SVN_ERR(authz_read_func(&readable, rev_root, key,
93251881Speter                              authz_read_baton, subpool));
94251881Speter      if (! readable)
95251881Speter        found_unreadable = TRUE;
96251881Speter      else
97251881Speter        found_readable = TRUE;
98251881Speter
99251881Speter      /* If we have at least one of each (readable/unreadable), we
100251881Speter         have our answer. */
101251881Speter      if (found_readable && found_unreadable)
102251881Speter        goto decision;
103251881Speter
104251881Speter      switch (change->change_kind)
105251881Speter        {
106251881Speter        case svn_fs_path_change_add:
107251881Speter        case svn_fs_path_change_replace:
108251881Speter          {
109251881Speter            const char *copyfrom_path;
110251881Speter            svn_revnum_t copyfrom_rev;
111251881Speter
112251881Speter            SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
113251881Speter                                       rev_root, key, subpool));
114251881Speter            if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
115251881Speter              {
116251881Speter                svn_fs_root_t *copyfrom_root;
117251881Speter                SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
118251881Speter                                             copyfrom_rev, subpool));
119251881Speter                SVN_ERR(authz_read_func(&readable,
120251881Speter                                        copyfrom_root, copyfrom_path,
121251881Speter                                        authz_read_baton, subpool));
122251881Speter                if (! readable)
123251881Speter                  found_unreadable = TRUE;
124251881Speter
125251881Speter                /* If we have at least one of each (readable/unreadable), we
126251881Speter                   have our answer. */
127251881Speter                if (found_readable && found_unreadable)
128251881Speter                  goto decision;
129251881Speter              }
130251881Speter          }
131251881Speter          break;
132251881Speter
133251881Speter        case svn_fs_path_change_delete:
134251881Speter        case svn_fs_path_change_modify:
135251881Speter        default:
136251881Speter          break;
137251881Speter        }
138251881Speter    }
139251881Speter
140251881Speter decision:
141251881Speter  svn_pool_destroy(subpool);
142251881Speter
143251881Speter  /* Either every changed path was unreadable... */
144251881Speter  if (! found_readable)
145251881Speter    *access_level = svn_repos_revision_access_none;
146251881Speter
147251881Speter  /* ... or some changed path was unreadable... */
148251881Speter  else if (found_unreadable)
149251881Speter    *access_level = svn_repos_revision_access_partial;
150251881Speter
151251881Speter  /* ... or every changed path was readable (the default). */
152251881Speter  return SVN_NO_ERROR;
153251881Speter}
154251881Speter
155251881Speter
156251881Speter/* Store as keys in CHANGED the paths of all node in ROOT that show a
157251881Speter * significant change.  "Significant" means that the text or
158251881Speter * properties of the node were changed, or that the node was added or
159251881Speter * deleted.
160251881Speter *
161251881Speter * The CHANGED hash set and its keys and values are allocated in POOL;
162251881Speter * keys are const char * paths and values are svn_log_changed_path_t.
163251881Speter *
164251881Speter * To prevent changes from being processed over and over again, the
165251881Speter * changed paths for ROOT may be passed in PREFETCHED_CHANGES.  If the
166251881Speter * latter is NULL, we will request the list inside this function.
167251881Speter *
168251881Speter * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
169251881Speter * AUTHZ_READ_BATON and FS) to check whether each changed-path (and
170251881Speter * copyfrom_path) is readable:
171251881Speter *
172251881Speter *     - If some paths are readable and some are not, then silently
173251881Speter *     omit the unreadable paths from the CHANGED hash, and return
174251881Speter *     SVN_ERR_AUTHZ_PARTIALLY_READABLE.
175251881Speter *
176251881Speter *     - If absolutely every changed-path (and copyfrom_path) is
177251881Speter *     unreadable, then return an empty CHANGED hash and
178251881Speter *     SVN_ERR_AUTHZ_UNREADABLE.  (This is to distinguish a revision
179251881Speter *     which truly has no changed paths from a revision in which all
180251881Speter *     paths are unreadable.)
181251881Speter */
182251881Speterstatic svn_error_t *
183251881Speterdetect_changed(apr_hash_t **changed,
184251881Speter               svn_fs_root_t *root,
185251881Speter               svn_fs_t *fs,
186251881Speter               apr_hash_t *prefetched_changes,
187251881Speter               svn_repos_authz_func_t authz_read_func,
188251881Speter               void *authz_read_baton,
189251881Speter               apr_pool_t *pool)
190251881Speter{
191251881Speter  apr_hash_t *changes = prefetched_changes;
192251881Speter  apr_hash_index_t *hi;
193251881Speter  apr_pool_t *subpool;
194251881Speter  svn_boolean_t found_readable = FALSE;
195251881Speter  svn_boolean_t found_unreadable = FALSE;
196251881Speter
197251881Speter  *changed = svn_hash__make(pool);
198251881Speter  if (changes == NULL)
199251881Speter    SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
200251881Speter
201251881Speter  if (apr_hash_count(changes) == 0)
202251881Speter    /* No paths changed in this revision?  Uh, sure, I guess the
203251881Speter       revision is readable, then.  */
204251881Speter    return SVN_NO_ERROR;
205251881Speter
206251881Speter  subpool = svn_pool_create(pool);
207251881Speter
208251881Speter  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
209251881Speter    {
210251881Speter      /* NOTE:  Much of this loop is going to look quite similar to
211251881Speter         svn_repos_check_revision_access(), but we have to do more things
212251881Speter         here, so we'll live with the duplication. */
213251881Speter      const void *key;
214251881Speter      void *val;
215251881Speter      svn_fs_path_change2_t *change;
216251881Speter      const char *path;
217251881Speter      char action;
218251881Speter      svn_log_changed_path2_t *item;
219251881Speter
220251881Speter      svn_pool_clear(subpool);
221251881Speter
222251881Speter      /* KEY will be the path, VAL the change. */
223251881Speter      apr_hash_this(hi, &key, NULL, &val);
224251881Speter      path = (const char *) key;
225251881Speter      change = val;
226251881Speter
227251881Speter      /* Skip path if unreadable. */
228251881Speter      if (authz_read_func)
229251881Speter        {
230251881Speter          svn_boolean_t readable;
231251881Speter          SVN_ERR(authz_read_func(&readable,
232251881Speter                                  root, path,
233251881Speter                                  authz_read_baton, subpool));
234251881Speter          if (! readable)
235251881Speter            {
236251881Speter              found_unreadable = TRUE;
237251881Speter              continue;
238251881Speter            }
239251881Speter        }
240251881Speter
241251881Speter      /* At least one changed-path was readable. */
242251881Speter      found_readable = TRUE;
243251881Speter
244251881Speter      switch (change->change_kind)
245251881Speter        {
246251881Speter        case svn_fs_path_change_reset:
247251881Speter          continue;
248251881Speter
249251881Speter        case svn_fs_path_change_add:
250251881Speter          action = 'A';
251251881Speter          break;
252251881Speter
253251881Speter        case svn_fs_path_change_replace:
254251881Speter          action = 'R';
255251881Speter          break;
256251881Speter
257251881Speter        case svn_fs_path_change_delete:
258251881Speter          action = 'D';
259251881Speter          break;
260251881Speter
261251881Speter        case svn_fs_path_change_modify:
262251881Speter        default:
263251881Speter          action = 'M';
264251881Speter          break;
265251881Speter        }
266251881Speter
267251881Speter      item = svn_log_changed_path2_create(pool);
268251881Speter      item->action = action;
269251881Speter      item->node_kind = change->node_kind;
270251881Speter      item->copyfrom_rev = SVN_INVALID_REVNUM;
271251881Speter      item->text_modified = change->text_mod ? svn_tristate_true
272251881Speter                                             : svn_tristate_false;
273251881Speter      item->props_modified = change->prop_mod ? svn_tristate_true
274251881Speter                                              : svn_tristate_false;
275251881Speter
276251881Speter      /* Pre-1.6 revision files don't store the change path kind, so fetch
277251881Speter         it manually. */
278251881Speter      if (item->node_kind == svn_node_unknown)
279251881Speter        {
280251881Speter          svn_fs_root_t *check_root = root;
281251881Speter          const char *check_path = path;
282251881Speter
283251881Speter          /* Deleted items don't exist so check earlier revision.  We
284251881Speter             know the parent must exist and could be a copy */
285251881Speter          if (change->change_kind == svn_fs_path_change_delete)
286251881Speter            {
287251881Speter              svn_fs_history_t *history;
288251881Speter              svn_revnum_t prev_rev;
289251881Speter              const char *parent_path, *name;
290251881Speter
291251881Speter              svn_fspath__split(&parent_path, &name, path, subpool);
292251881Speter
293251881Speter              SVN_ERR(svn_fs_node_history(&history, root, parent_path,
294251881Speter                                          subpool));
295251881Speter
296251881Speter              /* Two calls because the first call returns the original
297251881Speter                 revision as the deleted child means it is 'interesting' */
298251881Speter              SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
299251881Speter              SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
300251881Speter
301251881Speter              SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev, history,
302251881Speter                                              subpool));
303251881Speter              SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev, subpool));
304251881Speter              check_path = svn_fspath__join(parent_path, name, subpool);
305251881Speter            }
306251881Speter
307251881Speter          SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path,
308251881Speter                                    subpool));
309251881Speter        }
310251881Speter
311251881Speter
312251881Speter      if ((action == 'A') || (action == 'R'))
313251881Speter        {
314251881Speter          const char *copyfrom_path = change->copyfrom_path;
315251881Speter          svn_revnum_t copyfrom_rev = change->copyfrom_rev;
316251881Speter
317251881Speter          /* the following is a potentially expensive operation since on FSFS
318251881Speter             we will follow the DAG from ROOT to PATH and that requires
319251881Speter             actually reading the directories along the way. */
320251881Speter          if (!change->copyfrom_known)
321251881Speter            SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
322251881Speter                                      root, path, subpool));
323251881Speter
324251881Speter          if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
325251881Speter            {
326251881Speter              svn_boolean_t readable = TRUE;
327251881Speter
328251881Speter              if (authz_read_func)
329251881Speter                {
330251881Speter                  svn_fs_root_t *copyfrom_root;
331251881Speter
332251881Speter                  SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
333251881Speter                                               copyfrom_rev, subpool));
334251881Speter                  SVN_ERR(authz_read_func(&readable,
335251881Speter                                          copyfrom_root, copyfrom_path,
336251881Speter                                          authz_read_baton, subpool));
337251881Speter                  if (! readable)
338251881Speter                    found_unreadable = TRUE;
339251881Speter                }
340251881Speter
341251881Speter              if (readable)
342251881Speter                {
343251881Speter                  item->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
344251881Speter                  item->copyfrom_rev = copyfrom_rev;
345251881Speter                }
346251881Speter            }
347251881Speter        }
348251881Speter      svn_hash_sets(*changed, apr_pstrdup(pool, path), item);
349251881Speter    }
350251881Speter
351251881Speter  svn_pool_destroy(subpool);
352251881Speter
353251881Speter  if (! found_readable)
354251881Speter    /* Every changed-path was unreadable. */
355251881Speter    return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE,
356251881Speter                            NULL, NULL);
357251881Speter
358251881Speter  if (found_unreadable)
359251881Speter    /* At least one changed-path was unreadable. */
360251881Speter    return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE,
361251881Speter                            NULL, NULL);
362251881Speter
363251881Speter  /* Every changed-path was readable. */
364251881Speter  return SVN_NO_ERROR;
365251881Speter}
366251881Speter
367251881Speter/* This is used by svn_repos_get_logs to keep track of multiple
368251881Speter * path history information while working through history.
369251881Speter *
370251881Speter * The two pools are swapped after each iteration through history because
371251881Speter * to get the next history requires the previous one.
372251881Speter */
373251881Speterstruct path_info
374251881Speter{
375251881Speter  svn_stringbuf_t *path;
376251881Speter  svn_revnum_t history_rev;
377251881Speter  svn_boolean_t done;
378251881Speter  svn_boolean_t first_time;
379251881Speter
380251881Speter  /* If possible, we like to keep open the history object for each path,
381251881Speter     since it avoids needed to open and close it many times as we walk
382251881Speter     backwards in time.  To do so we need two pools, so that we can clear
383251881Speter     one each time through.  If we're not holding the history open for
384251881Speter     this path then these three pointers will be NULL. */
385251881Speter  svn_fs_history_t *hist;
386251881Speter  apr_pool_t *newpool;
387251881Speter  apr_pool_t *oldpool;
388251881Speter};
389251881Speter
390251881Speter/* Advance to the next history for the path.
391251881Speter *
392251881Speter * If INFO->HIST is not NULL we do this using that existing history object,
393251881Speter * otherwise we open a new one.
394251881Speter *
395251881Speter * If no more history is available or the history revision is less
396251881Speter * (earlier) than START, or the history is not available due
397251881Speter * to authorization, then INFO->DONE is set to TRUE.
398251881Speter *
399251881Speter * A STRICT value of FALSE will indicate to follow history across copied
400251881Speter * paths.
401251881Speter *
402251881Speter * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
403251881Speter * AUTHZ_READ_BATON and FS) to check whether INFO->PATH is still readable if
404251881Speter * we do indeed find more history for the path.
405251881Speter */
406251881Speterstatic svn_error_t *
407251881Speterget_history(struct path_info *info,
408251881Speter            svn_fs_t *fs,
409251881Speter            svn_boolean_t strict,
410251881Speter            svn_repos_authz_func_t authz_read_func,
411251881Speter            void *authz_read_baton,
412251881Speter            svn_revnum_t start,
413251881Speter            apr_pool_t *pool)
414251881Speter{
415251881Speter  svn_fs_root_t *history_root = NULL;
416251881Speter  svn_fs_history_t *hist;
417251881Speter  apr_pool_t *subpool;
418251881Speter  const char *path;
419251881Speter
420251881Speter  if (info->hist)
421251881Speter    {
422251881Speter      subpool = info->newpool;
423251881Speter
424251881Speter      SVN_ERR(svn_fs_history_prev(&info->hist, info->hist, ! strict, subpool));
425251881Speter
426251881Speter      hist = info->hist;
427251881Speter    }
428251881Speter  else
429251881Speter    {
430251881Speter      subpool = svn_pool_create(pool);
431251881Speter
432251881Speter      /* Open the history located at the last rev we were at. */
433251881Speter      SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev,
434251881Speter                                   subpool));
435251881Speter
436251881Speter      SVN_ERR(svn_fs_node_history(&hist, history_root, info->path->data,
437251881Speter                                  subpool));
438251881Speter
439251881Speter      SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
440251881Speter
441251881Speter      if (info->first_time)
442251881Speter        info->first_time = FALSE;
443251881Speter      else
444251881Speter        SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
445251881Speter    }
446251881Speter
447251881Speter  if (! hist)
448251881Speter    {
449251881Speter      svn_pool_destroy(subpool);
450251881Speter      if (info->oldpool)
451251881Speter        svn_pool_destroy(info->oldpool);
452251881Speter      info->done = TRUE;
453251881Speter      return SVN_NO_ERROR;
454251881Speter    }
455251881Speter
456251881Speter  /* Fetch the location information for this history step. */
457251881Speter  SVN_ERR(svn_fs_history_location(&path, &info->history_rev,
458251881Speter                                  hist, subpool));
459251881Speter
460251881Speter  svn_stringbuf_set(info->path, path);
461251881Speter
462251881Speter  /* If this history item predates our START revision then
463251881Speter     don't fetch any more for this path. */
464251881Speter  if (info->history_rev < start)
465251881Speter    {
466251881Speter      svn_pool_destroy(subpool);
467251881Speter      if (info->oldpool)
468251881Speter        svn_pool_destroy(info->oldpool);
469251881Speter      info->done = TRUE;
470251881Speter      return SVN_NO_ERROR;
471251881Speter    }
472251881Speter
473251881Speter  /* Is the history item readable?  If not, done with path. */
474251881Speter  if (authz_read_func)
475251881Speter    {
476251881Speter      svn_boolean_t readable;
477251881Speter      SVN_ERR(svn_fs_revision_root(&history_root, fs,
478251881Speter                                   info->history_rev,
479251881Speter                                   subpool));
480251881Speter      SVN_ERR(authz_read_func(&readable, history_root,
481251881Speter                              info->path->data,
482251881Speter                              authz_read_baton,
483251881Speter                              subpool));
484251881Speter      if (! readable)
485251881Speter        info->done = TRUE;
486251881Speter    }
487251881Speter
488251881Speter  if (! info->hist)
489251881Speter    {
490251881Speter      svn_pool_destroy(subpool);
491251881Speter    }
492251881Speter  else
493251881Speter    {
494251881Speter      apr_pool_t *temppool = info->oldpool;
495251881Speter      info->oldpool = info->newpool;
496251881Speter      svn_pool_clear(temppool);
497251881Speter      info->newpool = temppool;
498251881Speter    }
499251881Speter
500251881Speter  return SVN_NO_ERROR;
501251881Speter}
502251881Speter
503251881Speter/* Set INFO->HIST to the next history for the path *if* there is history
504251881Speter * available and INFO->HISTORY_REV is equal to or greater than CURRENT.
505251881Speter *
506251881Speter * *CHANGED is set to TRUE if the path has history in the CURRENT revision,
507251881Speter * otherwise it is not touched.
508251881Speter *
509251881Speter * If we do need to get the next history revision for the path, call
510251881Speter * get_history to do it -- see it for details.
511251881Speter */
512251881Speterstatic svn_error_t *
513251881Spetercheck_history(svn_boolean_t *changed,
514251881Speter              struct path_info *info,
515251881Speter              svn_fs_t *fs,
516251881Speter              svn_revnum_t current,
517251881Speter              svn_boolean_t strict,
518251881Speter              svn_repos_authz_func_t authz_read_func,
519251881Speter              void *authz_read_baton,
520251881Speter              svn_revnum_t start,
521251881Speter              apr_pool_t *pool)
522251881Speter{
523251881Speter  /* If we're already done with histories for this path,
524251881Speter     don't try to fetch any more. */
525251881Speter  if (info->done)
526251881Speter    return SVN_NO_ERROR;
527251881Speter
528251881Speter  /* If the last rev we got for this path is less than CURRENT,
529251881Speter     then just return and don't fetch history for this path.
530251881Speter     The caller will get to this rev eventually or else reach
531251881Speter     the limit. */
532251881Speter  if (info->history_rev < current)
533251881Speter    return SVN_NO_ERROR;
534251881Speter
535251881Speter  /* If the last rev we got for this path is equal to CURRENT
536251881Speter     then set *CHANGED to true and get the next history
537251881Speter     rev where this path was changed. */
538251881Speter  *changed = TRUE;
539251881Speter  return get_history(info, fs, strict, authz_read_func,
540251881Speter                     authz_read_baton, start, pool);
541251881Speter}
542251881Speter
543251881Speter/* Return the next interesting revision in our list of HISTORIES. */
544251881Speterstatic svn_revnum_t
545251881Speternext_history_rev(const apr_array_header_t *histories)
546251881Speter{
547251881Speter  svn_revnum_t next_rev = SVN_INVALID_REVNUM;
548251881Speter  int i;
549251881Speter
550251881Speter  for (i = 0; i < histories->nelts; ++i)
551251881Speter    {
552251881Speter      struct path_info *info = APR_ARRAY_IDX(histories, i,
553251881Speter                                             struct path_info *);
554251881Speter      if (info->done)
555251881Speter        continue;
556251881Speter      if (info->history_rev > next_rev)
557251881Speter        next_rev = info->history_rev;
558251881Speter    }
559251881Speter
560251881Speter  return next_rev;
561251881Speter}
562251881Speter
563251881Speter/* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to
564251881Speter   catalogs describing how mergeinfo values on paths (which are the
565251881Speter   keys of those catalogs) were changed in REV.  If *PREFETCHED_CAHNGES
566251881Speter   already contains the changed paths for REV, use that.  Otherwise,
567251881Speter   request that data and return it in *PREFETCHED_CHANGES. */
568251881Speter/* ### TODO: This would make a *great*, useful public function,
569251881Speter   ### svn_repos_fs_mergeinfo_changed()!  -- cmpilato  */
570251881Speterstatic svn_error_t *
571251881Speterfs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
572251881Speter                     svn_mergeinfo_catalog_t *added_mergeinfo_catalog,
573251881Speter                     apr_hash_t **prefetched_changes,
574251881Speter                     svn_fs_t *fs,
575251881Speter                     svn_revnum_t rev,
576251881Speter                     apr_pool_t *result_pool,
577251881Speter                     apr_pool_t *scratch_pool)
578251881Speter
579251881Speter{
580251881Speter  svn_fs_root_t *root;
581251881Speter  apr_pool_t *iterpool;
582251881Speter  apr_hash_index_t *hi;
583251881Speter
584251881Speter  /* Initialize return variables. */
585251881Speter  *deleted_mergeinfo_catalog = svn_hash__make(result_pool);
586251881Speter  *added_mergeinfo_catalog = svn_hash__make(result_pool);
587251881Speter
588251881Speter  /* Revision 0 has no mergeinfo and no mergeinfo changes. */
589251881Speter  if (rev == 0)
590251881Speter    return SVN_NO_ERROR;
591251881Speter
592251881Speter  /* We're going to use the changed-paths information for REV to
593251881Speter     narrow down our search. */
594251881Speter  SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
595251881Speter  if (*prefetched_changes == NULL)
596251881Speter    SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool));
597251881Speter
598251881Speter  /* No changed paths?  We're done. */
599251881Speter  if (apr_hash_count(*prefetched_changes) == 0)
600251881Speter    return SVN_NO_ERROR;
601251881Speter
602251881Speter  /* Loop over changes, looking for anything that might carry an
603251881Speter     svn:mergeinfo change and is one of our paths of interest, or a
604251881Speter     child or [grand]parent directory thereof. */
605251881Speter  iterpool = svn_pool_create(scratch_pool);
606251881Speter  for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
607251881Speter       hi;
608251881Speter       hi = apr_hash_next(hi))
609251881Speter    {
610251881Speter      const void *key;
611251881Speter      void *val;
612251881Speter      svn_fs_path_change2_t *change;
613251881Speter      const char *changed_path, *base_path = NULL;
614251881Speter      svn_revnum_t base_rev = SVN_INVALID_REVNUM;
615251881Speter      svn_fs_root_t *base_root = NULL;
616251881Speter      svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value;
617251881Speter
618251881Speter      svn_pool_clear(iterpool);
619251881Speter
620251881Speter      /* KEY will be the path, VAL the change. */
621251881Speter      apr_hash_this(hi, &key, NULL, &val);
622251881Speter      changed_path = key;
623251881Speter      change = val;
624251881Speter
625251881Speter      /* If there was no property change on this item, ignore it. */
626251881Speter      if (! change->prop_mod)
627251881Speter        continue;
628251881Speter
629251881Speter      switch (change->change_kind)
630251881Speter        {
631251881Speter
632251881Speter        /* ### TODO: Can the add, replace, and modify cases be joined
633251881Speter           ### together to all use svn_repos__prev_location()?  The
634251881Speter           ### difference would be the fallback case (path/rev-1 for
635251881Speter           ### modifies, NULL otherwise).  -- cmpilato  */
636251881Speter
637251881Speter        /* If the path was added or replaced, see if it was created via
638251881Speter           copy.  If so, that will tell us where its previous location
639251881Speter           was.  If not, there's no previous location to examine.  */
640251881Speter        case svn_fs_path_change_add:
641251881Speter        case svn_fs_path_change_replace:
642251881Speter          {
643251881Speter            const char *copyfrom_path;
644251881Speter            svn_revnum_t copyfrom_rev;
645251881Speter
646251881Speter            SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
647251881Speter                                       root, changed_path, iterpool));
648251881Speter            if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
649251881Speter              {
650251881Speter                base_path = apr_pstrdup(scratch_pool, copyfrom_path);
651251881Speter                base_rev = copyfrom_rev;
652251881Speter              }
653251881Speter            break;
654251881Speter          }
655251881Speter
656251881Speter        /* If the path was merely modified, see if its previous
657251881Speter           location was affected by a copy which happened in this
658251881Speter           revision before assuming it holds the same path it did the
659251881Speter           previous revision. */
660251881Speter        case svn_fs_path_change_modify:
661251881Speter          {
662251881Speter            svn_revnum_t appeared_rev;
663251881Speter
664251881Speter            SVN_ERR(svn_repos__prev_location(&appeared_rev, &base_path,
665251881Speter                                             &base_rev, fs, rev,
666251881Speter                                             changed_path, iterpool));
667251881Speter
668251881Speter            /* If this path isn't the result of a copy that occurred
669251881Speter               in this revision, we can find the previous version of
670251881Speter               it in REV - 1 at the same path. */
671251881Speter            if (! (base_path && SVN_IS_VALID_REVNUM(base_rev)
672251881Speter                   && (appeared_rev == rev)))
673251881Speter              {
674251881Speter                base_path = changed_path;
675251881Speter                base_rev = rev - 1;
676251881Speter              }
677251881Speter            break;
678251881Speter          }
679251881Speter
680251881Speter        /* We don't care about any of the other cases. */
681251881Speter        case svn_fs_path_change_delete:
682251881Speter        case svn_fs_path_change_reset:
683251881Speter        default:
684251881Speter          continue;
685251881Speter        }
686251881Speter
687251881Speter      /* If there was a base location, fetch its mergeinfo property value. */
688251881Speter      if (base_path && SVN_IS_VALID_REVNUM(base_rev))
689251881Speter        {
690251881Speter          SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, iterpool));
691251881Speter          SVN_ERR(svn_fs_node_prop(&prev_mergeinfo_value, base_root, base_path,
692251881Speter                                   SVN_PROP_MERGEINFO, iterpool));
693251881Speter        }
694251881Speter
695251881Speter      /* Now fetch the current (as of REV) mergeinfo property value. */
696251881Speter      SVN_ERR(svn_fs_node_prop(&mergeinfo_value, root, changed_path,
697251881Speter                               SVN_PROP_MERGEINFO, iterpool));
698251881Speter
699251881Speter      /* No mergeinfo on either the new or previous location?  Just
700251881Speter         skip it.  (If there *was* a change, it would have been in
701251881Speter         inherited mergeinfo only, which should be picked up by the
702251881Speter         iteration of this loop that finds the parent paths that
703251881Speter         really got changed.)  */
704251881Speter      if (! (mergeinfo_value || prev_mergeinfo_value))
705251881Speter        continue;
706251881Speter
707251881Speter      /* If mergeinfo was explicitly added or removed on this path, we
708251881Speter         need to check to see if that was a real semantic change of
709251881Speter         meaning.  So, fill in the "missing" mergeinfo value with the
710251881Speter         inherited mergeinfo for that path/revision.  */
711251881Speter      if (prev_mergeinfo_value && (! mergeinfo_value))
712251881Speter        {
713251881Speter          apr_array_header_t *query_paths =
714251881Speter            apr_array_make(iterpool, 1, sizeof(const char *));
715251881Speter          svn_mergeinfo_t tmp_mergeinfo;
716251881Speter          svn_mergeinfo_catalog_t tmp_catalog;
717251881Speter
718251881Speter          APR_ARRAY_PUSH(query_paths, const char *) = changed_path;
719251881Speter          SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root,
720251881Speter                                        query_paths, svn_mergeinfo_inherited,
721251881Speter                                        FALSE, TRUE, iterpool, iterpool));
722251881Speter          tmp_mergeinfo = svn_hash_gets(tmp_catalog, changed_path);
723251881Speter          if (tmp_mergeinfo)
724251881Speter            SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_value,
725251881Speter                                            tmp_mergeinfo,
726251881Speter                                            iterpool));
727251881Speter        }
728251881Speter      else if (mergeinfo_value && (! prev_mergeinfo_value)
729251881Speter               && base_path && SVN_IS_VALID_REVNUM(base_rev))
730251881Speter        {
731251881Speter          apr_array_header_t *query_paths =
732251881Speter            apr_array_make(iterpool, 1, sizeof(const char *));
733251881Speter          svn_mergeinfo_t tmp_mergeinfo;
734251881Speter          svn_mergeinfo_catalog_t tmp_catalog;
735251881Speter
736251881Speter          APR_ARRAY_PUSH(query_paths, const char *) = base_path;
737251881Speter          SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, base_root,
738251881Speter                                        query_paths, svn_mergeinfo_inherited,
739251881Speter                                        FALSE, TRUE, iterpool, iterpool));
740251881Speter          tmp_mergeinfo = svn_hash_gets(tmp_catalog, base_path);
741251881Speter          if (tmp_mergeinfo)
742251881Speter            SVN_ERR(svn_mergeinfo_to_string(&prev_mergeinfo_value,
743251881Speter                                            tmp_mergeinfo,
744251881Speter                                            iterpool));
745251881Speter        }
746251881Speter
747251881Speter      /* If the old and new mergeinfo differ in any way, store the
748251881Speter         before and after mergeinfo values in our return hashes. */
749251881Speter      if ((prev_mergeinfo_value && (! mergeinfo_value))
750251881Speter          || ((! prev_mergeinfo_value) && mergeinfo_value)
751251881Speter          || (prev_mergeinfo_value && mergeinfo_value
752251881Speter              && (! svn_string_compare(mergeinfo_value,
753251881Speter                                       prev_mergeinfo_value))))
754251881Speter        {
755251881Speter          svn_mergeinfo_t prev_mergeinfo = NULL, mergeinfo = NULL;
756251881Speter          svn_mergeinfo_t deleted, added;
757251881Speter          const char *hash_path;
758251881Speter
759251881Speter          if (mergeinfo_value)
760251881Speter            SVN_ERR(svn_mergeinfo_parse(&mergeinfo,
761251881Speter                                        mergeinfo_value->data, iterpool));
762251881Speter          if (prev_mergeinfo_value)
763251881Speter            SVN_ERR(svn_mergeinfo_parse(&prev_mergeinfo,
764251881Speter                                        prev_mergeinfo_value->data, iterpool));
765251881Speter          SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
766251881Speter                                      mergeinfo, FALSE, result_pool,
767251881Speter                                      iterpool));
768251881Speter
769251881Speter          /* Toss interesting stuff into our return catalogs. */
770251881Speter          hash_path = apr_pstrdup(result_pool, changed_path);
771251881Speter          svn_hash_sets(*deleted_mergeinfo_catalog, hash_path, deleted);
772251881Speter          svn_hash_sets(*added_mergeinfo_catalog, hash_path, added);
773251881Speter        }
774251881Speter    }
775251881Speter
776251881Speter  svn_pool_destroy(iterpool);
777251881Speter  return SVN_NO_ERROR;
778251881Speter}
779251881Speter
780251881Speter
781251881Speter/* Determine what (if any) mergeinfo for PATHS was modified in
782251881Speter   revision REV, returning the differences for added mergeinfo in
783251881Speter   *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO.
784251881Speter   If *PREFETCHED_CAHNGES already contains the changed paths for
785251881Speter   REV, use that.  Otherwise, request that data and return it in
786251881Speter   *PREFETCHED_CHANGES.
787251881Speter   Use POOL for all allocations. */
788251881Speterstatic svn_error_t *
789251881Speterget_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
790251881Speter                               svn_mergeinfo_t *deleted_mergeinfo,
791251881Speter                               apr_hash_t **prefetched_changes,
792251881Speter                               svn_fs_t *fs,
793251881Speter                               const apr_array_header_t *paths,
794251881Speter                               svn_revnum_t rev,
795251881Speter                               apr_pool_t *result_pool,
796251881Speter                               apr_pool_t *scratch_pool)
797251881Speter{
798251881Speter  svn_mergeinfo_catalog_t added_mergeinfo_catalog, deleted_mergeinfo_catalog;
799251881Speter  apr_hash_index_t *hi;
800251881Speter  svn_fs_root_t *root;
801251881Speter  apr_pool_t *iterpool;
802251881Speter  int i;
803251881Speter  svn_error_t *err;
804251881Speter
805251881Speter  /* Initialize return value. */
806251881Speter  *added_mergeinfo = svn_hash__make(result_pool);
807251881Speter  *deleted_mergeinfo = svn_hash__make(result_pool);
808251881Speter
809251881Speter  /* If we're asking about revision 0, there's no mergeinfo to be found. */
810251881Speter  if (rev == 0)
811251881Speter    return SVN_NO_ERROR;
812251881Speter
813251881Speter  /* No paths?  No mergeinfo. */
814251881Speter  if (! paths->nelts)
815251881Speter    return SVN_NO_ERROR;
816251881Speter
817251881Speter  /* Create a work subpool and get a root for REV. */
818251881Speter  SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
819251881Speter
820251881Speter  /* Fetch the mergeinfo changes for REV. */
821251881Speter  err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog,
822251881Speter                             &added_mergeinfo_catalog,
823251881Speter                             prefetched_changes,
824251881Speter                             fs, rev, scratch_pool, scratch_pool);
825251881Speter  if (err)
826251881Speter    {
827251881Speter      if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
828251881Speter        {
829251881Speter          /* Issue #3896: If invalid mergeinfo is encountered the
830251881Speter             best we can do is ignore it and act as if there were
831251881Speter             no mergeinfo modifications. */
832251881Speter          svn_error_clear(err);
833251881Speter          return SVN_NO_ERROR;
834251881Speter        }
835251881Speter      else
836251881Speter        {
837251881Speter          return svn_error_trace(err);
838251881Speter        }
839251881Speter    }
840251881Speter
841251881Speter  /* In most revisions, there will be no mergeinfo change at all. */
842251881Speter  if (   apr_hash_count(deleted_mergeinfo_catalog) == 0
843251881Speter      && apr_hash_count(added_mergeinfo_catalog) == 0)
844251881Speter    return SVN_NO_ERROR;
845251881Speter
846251881Speter  /* Check our PATHS for any changes to their inherited mergeinfo.
847251881Speter     (We deal with changes to mergeinfo directly *on* the paths in the
848251881Speter     following loop.)  */
849251881Speter  iterpool = svn_pool_create(scratch_pool);
850251881Speter  for (i = 0; i < paths->nelts; i++)
851251881Speter    {
852251881Speter      const char *path = APR_ARRAY_IDX(paths, i, const char *);
853251881Speter      const char *prev_path;
854251881Speter      apr_ssize_t klen;
855251881Speter      svn_revnum_t appeared_rev, prev_rev;
856251881Speter      svn_fs_root_t *prev_root;
857251881Speter      svn_mergeinfo_catalog_t catalog, inherited_catalog;
858251881Speter      svn_mergeinfo_t prev_mergeinfo, mergeinfo, deleted, added,
859251881Speter        prev_inherited_mergeinfo, inherited_mergeinfo;
860251881Speter      apr_array_header_t *query_paths;
861251881Speter
862251881Speter      svn_pool_clear(iterpool);
863251881Speter
864251881Speter      /* If this path is represented in the changed-mergeinfo hashes,
865251881Speter         we'll deal with it in the loop below. */
866251881Speter      if (svn_hash_gets(deleted_mergeinfo_catalog, path))
867251881Speter        continue;
868251881Speter
869251881Speter      /* Figure out what path/rev to compare against.  Ignore
870251881Speter         not-found errors returned by the filesystem.  */
871251881Speter      err = svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
872251881Speter                                     fs, rev, path, iterpool);
873251881Speter      if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
874251881Speter                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))
875251881Speter        {
876251881Speter          svn_error_clear(err);
877251881Speter          err = SVN_NO_ERROR;
878251881Speter          continue;
879251881Speter        }
880251881Speter      SVN_ERR(err);
881251881Speter
882251881Speter      /* If this path isn't the result of a copy that occurred in this
883251881Speter         revision, we can find the previous version of it in REV - 1
884251881Speter         at the same path. */
885251881Speter      if (! (prev_path && SVN_IS_VALID_REVNUM(prev_rev)
886251881Speter             && (appeared_rev == rev)))
887251881Speter        {
888251881Speter          prev_path = path;
889251881Speter          prev_rev = rev - 1;
890251881Speter        }
891251881Speter
892251881Speter      /* Fetch the previous mergeinfo (including inherited stuff) for
893251881Speter         this path.  Ignore not-found errors returned by the
894251881Speter         filesystem or invalid mergeinfo (Issue #3896).*/
895251881Speter      SVN_ERR(svn_fs_revision_root(&prev_root, fs, prev_rev, iterpool));
896251881Speter      query_paths = apr_array_make(iterpool, 1, sizeof(const char *));
897251881Speter      APR_ARRAY_PUSH(query_paths, const char *) = prev_path;
898251881Speter      err = svn_fs_get_mergeinfo2(&catalog, prev_root, query_paths,
899251881Speter                                  svn_mergeinfo_inherited, FALSE, TRUE,
900251881Speter                                  iterpool, iterpool);
901251881Speter      if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
902251881Speter                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
903251881Speter                  err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
904251881Speter        {
905251881Speter          svn_error_clear(err);
906251881Speter          err = SVN_NO_ERROR;
907251881Speter          continue;
908251881Speter        }
909251881Speter      SVN_ERR(err);
910251881Speter
911251881Speter      /* Issue #4022 'svn log -g interprets change in inherited mergeinfo due
912251881Speter         to move as a merge': A copy where the source and destination inherit
913251881Speter         mergeinfo from the same parent means the inherited mergeinfo of the
914251881Speter         source and destination will differ, but this diffrence is not
915251881Speter         indicative of a merge unless the mergeinfo on the inherited parent
916251881Speter         has actually changed.
917251881Speter
918251881Speter         To check for this we must fetch the "raw" previous inherited
919251881Speter         mergeinfo and the "raw" mergeinfo @REV then compare these. */
920251881Speter      SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, prev_root, query_paths,
921251881Speter                                    svn_mergeinfo_nearest_ancestor, FALSE,
922251881Speter                                    FALSE, /* adjust_inherited_mergeinfo */
923251881Speter                                    iterpool, iterpool));
924251881Speter
925251881Speter      klen = strlen(prev_path);
926251881Speter      prev_mergeinfo = apr_hash_get(catalog, prev_path, klen);
927251881Speter      prev_inherited_mergeinfo = apr_hash_get(inherited_catalog, prev_path, klen);
928251881Speter
929251881Speter      /* Fetch the current mergeinfo (as of REV, and including
930251881Speter         inherited stuff) for this path. */
931251881Speter      APR_ARRAY_IDX(query_paths, 0, const char *) = path;
932251881Speter      SVN_ERR(svn_fs_get_mergeinfo2(&catalog, root, query_paths,
933251881Speter                                    svn_mergeinfo_inherited, FALSE, TRUE,
934251881Speter                                    iterpool, iterpool));
935251881Speter
936251881Speter      /* Issue #4022 again, fetch the raw inherited mergeinfo. */
937251881Speter      SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, root, query_paths,
938251881Speter                                    svn_mergeinfo_nearest_ancestor, FALSE,
939251881Speter                                    FALSE, /* adjust_inherited_mergeinfo */
940251881Speter                                    iterpool, iterpool));
941251881Speter
942251881Speter      klen = strlen(path);
943251881Speter      mergeinfo = apr_hash_get(catalog, path, klen);
944251881Speter      inherited_mergeinfo = apr_hash_get(inherited_catalog, path, klen);
945251881Speter
946251881Speter      if (!prev_mergeinfo && !mergeinfo)
947251881Speter        continue;
948251881Speter
949251881Speter      /* Last bit of issue #4022 checking. */
950251881Speter      if (prev_inherited_mergeinfo && inherited_mergeinfo)
951251881Speter        {
952251881Speter          svn_boolean_t inherits_same_mergeinfo;
953251881Speter
954251881Speter          SVN_ERR(svn_mergeinfo__equals(&inherits_same_mergeinfo,
955251881Speter                                        prev_inherited_mergeinfo,
956251881Speter                                        inherited_mergeinfo,
957251881Speter                                        TRUE, iterpool));
958251881Speter          /* If a copy rather than an actual merge brought about an
959251881Speter             inherited mergeinfo change then we are finished. */
960251881Speter          if (inherits_same_mergeinfo)
961251881Speter            continue;
962251881Speter        }
963251881Speter      else
964251881Speter        {
965251881Speter          svn_boolean_t same_mergeinfo;
966251881Speter          SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo,
967251881Speter                                        prev_inherited_mergeinfo,
968251881Speter                                        FALSE,
969251881Speter                                        TRUE, iterpool));
970251881Speter          if (same_mergeinfo)
971251881Speter            continue;
972251881Speter        }
973251881Speter
974251881Speter      /* Compare, constrast, and combine the results. */
975251881Speter      SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
976251881Speter                                  mergeinfo, FALSE, result_pool, iterpool));
977251881Speter      SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, deleted,
978251881Speter                                   result_pool, iterpool));
979251881Speter      SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, added,
980251881Speter                                   result_pool, iterpool));
981251881Speter     }
982251881Speter
983251881Speter  /* Merge all the mergeinfos which are, or are children of, one of
984251881Speter     our paths of interest into one giant delta mergeinfo.  */
985251881Speter  for (hi = apr_hash_first(scratch_pool, added_mergeinfo_catalog);
986251881Speter       hi; hi = apr_hash_next(hi))
987251881Speter    {
988251881Speter      const void *key;
989251881Speter      apr_ssize_t klen;
990251881Speter      void *val;
991251881Speter      const char *changed_path;
992251881Speter      svn_mergeinfo_t added, deleted;
993251881Speter
994251881Speter      /* The path is the key, the mergeinfo delta is the value. */
995251881Speter      apr_hash_this(hi, &key, &klen, &val);
996251881Speter      changed_path = key;
997251881Speter      added = val;
998251881Speter
999251881Speter      for (i = 0; i < paths->nelts; i++)
1000251881Speter        {
1001251881Speter          const char *path = APR_ARRAY_IDX(paths, i, const char *);
1002251881Speter          if (! svn_fspath__skip_ancestor(path, changed_path))
1003251881Speter            continue;
1004251881Speter          svn_pool_clear(iterpool);
1005251881Speter          deleted = apr_hash_get(deleted_mergeinfo_catalog, key, klen);
1006251881Speter          SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo,
1007251881Speter                                       svn_mergeinfo_dup(deleted, result_pool),
1008251881Speter                                       result_pool, iterpool));
1009251881Speter          SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo,
1010251881Speter                                       svn_mergeinfo_dup(added, result_pool),
1011251881Speter                                       result_pool, iterpool));
1012251881Speter
1013251881Speter          break;
1014251881Speter        }
1015251881Speter    }
1016251881Speter
1017251881Speter  svn_pool_destroy(iterpool);
1018251881Speter  return SVN_NO_ERROR;
1019251881Speter}
1020251881Speter
1021251881Speter
1022251881Speter/* Fill LOG_ENTRY with history information in FS at REV. */
1023251881Speterstatic svn_error_t *
1024251881Speterfill_log_entry(svn_log_entry_t *log_entry,
1025251881Speter               svn_revnum_t rev,
1026251881Speter               svn_fs_t *fs,
1027251881Speter               apr_hash_t *prefetched_changes,
1028251881Speter               svn_boolean_t discover_changed_paths,
1029251881Speter               const apr_array_header_t *revprops,
1030251881Speter               svn_repos_authz_func_t authz_read_func,
1031251881Speter               void *authz_read_baton,
1032251881Speter               apr_pool_t *pool)
1033251881Speter{
1034251881Speter  apr_hash_t *r_props, *changed_paths = NULL;
1035251881Speter  svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
1036251881Speter
1037251881Speter  /* Discover changed paths if the user requested them
1038251881Speter     or if we need to check that they are readable. */
1039251881Speter  if ((rev > 0)
1040251881Speter      && (authz_read_func || discover_changed_paths))
1041251881Speter    {
1042251881Speter      svn_fs_root_t *newroot;
1043251881Speter      svn_error_t *patherr;
1044251881Speter
1045251881Speter      SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
1046251881Speter      patherr = detect_changed(&changed_paths,
1047251881Speter                               newroot, fs, prefetched_changes,
1048251881Speter                               authz_read_func, authz_read_baton,
1049251881Speter                               pool);
1050251881Speter
1051251881Speter      if (patherr
1052251881Speter          && patherr->apr_err == SVN_ERR_AUTHZ_UNREADABLE)
1053251881Speter        {
1054251881Speter          /* All changed-paths are unreadable, so clear all fields. */
1055251881Speter          svn_error_clear(patherr);
1056251881Speter          changed_paths = NULL;
1057251881Speter          get_revprops = FALSE;
1058251881Speter        }
1059251881Speter      else if (patherr
1060251881Speter               && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
1061251881Speter        {
1062251881Speter          /* At least one changed-path was unreadable, so censor all
1063251881Speter             but author and date.  (The unreadable paths are already
1064251881Speter             missing from the hash.) */
1065251881Speter          svn_error_clear(patherr);
1066251881Speter          censor_revprops = TRUE;
1067251881Speter        }
1068251881Speter      else if (patherr)
1069251881Speter        return patherr;
1070251881Speter
1071251881Speter      /* It may be the case that an authz func was passed in, but
1072251881Speter         the user still doesn't want to see any changed-paths. */
1073251881Speter      if (! discover_changed_paths)
1074251881Speter        changed_paths = NULL;
1075251881Speter    }
1076251881Speter
1077251881Speter  if (get_revprops)
1078251881Speter    {
1079251881Speter      /* User is allowed to see at least some revprops. */
1080251881Speter      SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
1081251881Speter      if (revprops == NULL)
1082251881Speter        {
1083251881Speter          /* Requested all revprops... */
1084251881Speter          if (censor_revprops)
1085251881Speter            {
1086251881Speter              /* ... but we can only return author/date. */
1087251881Speter              log_entry->revprops = svn_hash__make(pool);
1088251881Speter              svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1089251881Speter                            svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR));
1090251881Speter              svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
1091251881Speter                            svn_hash_gets(r_props, SVN_PROP_REVISION_DATE));
1092251881Speter            }
1093251881Speter          else
1094251881Speter            /* ... so return all we got. */
1095251881Speter            log_entry->revprops = r_props;
1096251881Speter        }
1097251881Speter      else
1098251881Speter        {
1099251881Speter          /* Requested only some revprops... */
1100251881Speter          int i;
1101251881Speter          for (i = 0; i < revprops->nelts; i++)
1102251881Speter            {
1103251881Speter              char *name = APR_ARRAY_IDX(revprops, i, char *);
1104251881Speter              svn_string_t *value = svn_hash_gets(r_props, name);
1105251881Speter              if (censor_revprops
1106251881Speter                  && !(strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0
1107251881Speter                       || strcmp(name, SVN_PROP_REVISION_DATE) == 0))
1108251881Speter                /* ... but we can only return author/date. */
1109251881Speter                continue;
1110251881Speter              if (log_entry->revprops == NULL)
1111251881Speter                log_entry->revprops = svn_hash__make(pool);
1112251881Speter              svn_hash_sets(log_entry->revprops, name, value);
1113251881Speter            }
1114251881Speter        }
1115251881Speter    }
1116251881Speter
1117251881Speter  log_entry->changed_paths = changed_paths;
1118251881Speter  log_entry->changed_paths2 = changed_paths;
1119251881Speter  log_entry->revision = rev;
1120251881Speter
1121251881Speter  return SVN_NO_ERROR;
1122251881Speter}
1123251881Speter
1124251881Speter/* Send a log message for REV to RECEIVER with its RECEIVER_BATON.
1125251881Speter
1126251881Speter   FS is used with REV to fetch the interesting history information,
1127251881Speter   such as changed paths, revprops, etc.
1128251881Speter
1129251881Speter   The detect_changed function is used if either AUTHZ_READ_FUNC is
1130251881Speter   not NULL, or if DISCOVER_CHANGED_PATHS is TRUE.  See it for details.
1131251881Speter
1132251881Speter   If DESCENDING_ORDER is true, send child messages in descending order.
1133251881Speter
1134251881Speter   If REVPROPS is NULL, retrieve all revision properties; else, retrieve
1135251881Speter   only the revision properties named by the (const char *) array elements
1136251881Speter   (i.e. retrieve none if the array is empty).
1137251881Speter
1138251881Speter   LOG_TARGET_HISTORY_AS_MERGEINFO, HANDLING_MERGED_REVISION, and
1139251881Speter   NESTED_MERGES are as per the arguments of the same name to DO_LOGS.  If
1140251881Speter   HANDLING_MERGED_REVISION is true and *all* changed paths within REV are
1141251881Speter   already represented in LOG_TARGET_HISTORY_AS_MERGEINFO, then don't send
1142251881Speter   the log message for REV.  If SUBTRACTIVE_MERGE is true, then REV was
1143251881Speter   reverse merged.
1144251881Speter
1145251881Speter   If HANDLING_MERGED_REVISIONS is FALSE then ignore NESTED_MERGES.  Otherwise
1146251881Speter   if NESTED_MERGES is not NULL and REV is contained in it, then don't send
1147251881Speter   the log for REV, otherwise send it normally and add REV to
1148251881Speter   NESTED_MERGES. */
1149251881Speterstatic svn_error_t *
1150251881Spetersend_log(svn_revnum_t rev,
1151251881Speter         svn_fs_t *fs,
1152251881Speter         apr_hash_t *prefetched_changes,
1153251881Speter         svn_mergeinfo_t log_target_history_as_mergeinfo,
1154251881Speter         apr_hash_t *nested_merges,
1155251881Speter         svn_boolean_t discover_changed_paths,
1156251881Speter         svn_boolean_t subtractive_merge,
1157251881Speter         svn_boolean_t handling_merged_revision,
1158251881Speter         const apr_array_header_t *revprops,
1159251881Speter         svn_boolean_t has_children,
1160251881Speter         svn_log_entry_receiver_t receiver,
1161251881Speter         void *receiver_baton,
1162251881Speter         svn_repos_authz_func_t authz_read_func,
1163251881Speter         void *authz_read_baton,
1164251881Speter         apr_pool_t *pool)
1165251881Speter{
1166251881Speter  svn_log_entry_t *log_entry;
1167251881Speter  /* Assume we want to send the log for REV. */
1168251881Speter  svn_boolean_t found_rev_of_interest = TRUE;
1169251881Speter
1170251881Speter  log_entry = svn_log_entry_create(pool);
1171251881Speter  SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes,
1172251881Speter                         discover_changed_paths || handling_merged_revision,
1173251881Speter                         revprops, authz_read_func, authz_read_baton,
1174251881Speter                         pool));
1175251881Speter  log_entry->has_children = has_children;
1176251881Speter  log_entry->subtractive_merge = subtractive_merge;
1177251881Speter
1178251881Speter  /* Is REV a merged revision that is already part of
1179251881Speter     LOG_TARGET_HISTORY_AS_MERGEINFO?  If so then there is no
1180251881Speter     need to send it, since it already was (or will be) sent. */
1181251881Speter  if (handling_merged_revision
1182251881Speter      && log_entry->changed_paths2
1183251881Speter      && log_target_history_as_mergeinfo
1184251881Speter      && apr_hash_count(log_target_history_as_mergeinfo))
1185251881Speter    {
1186251881Speter      apr_hash_index_t *hi;
1187251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
1188251881Speter
1189251881Speter      /* REV was merged in, but it might already be part of the log target's
1190251881Speter         natural history, so change our starting assumption. */
1191251881Speter      found_rev_of_interest = FALSE;
1192251881Speter
1193251881Speter      /* Look at each changed path in REV. */
1194251881Speter      for (hi = apr_hash_first(subpool, log_entry->changed_paths2);
1195251881Speter           hi;
1196251881Speter           hi = apr_hash_next(hi))
1197251881Speter        {
1198251881Speter          svn_boolean_t path_is_in_history = FALSE;
1199251881Speter          const char *changed_path = svn__apr_hash_index_key(hi);
1200251881Speter          apr_hash_index_t *hi2;
1201251881Speter          apr_pool_t *inner_subpool = svn_pool_create(subpool);
1202251881Speter
1203251881Speter          /* Look at each path on the log target's mergeinfo. */
1204251881Speter          for (hi2 = apr_hash_first(inner_subpool,
1205251881Speter                                    log_target_history_as_mergeinfo);
1206251881Speter               hi2;
1207251881Speter               hi2 = apr_hash_next(hi2))
1208251881Speter            {
1209251881Speter              const char *mergeinfo_path =
1210251881Speter                svn__apr_hash_index_key(hi2);
1211251881Speter              svn_rangelist_t *rangelist =
1212251881Speter                svn__apr_hash_index_val(hi2);
1213251881Speter
1214251881Speter              /* Check whether CHANGED_PATH at revision REV is a child of
1215251881Speter                 a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
1216251881Speter              if (svn_fspath__skip_ancestor(mergeinfo_path, changed_path))
1217251881Speter                {
1218251881Speter                  int i;
1219251881Speter
1220251881Speter                  for (i = 0; i < rangelist->nelts; i++)
1221251881Speter                    {
1222251881Speter                      svn_merge_range_t *range =
1223251881Speter                        APR_ARRAY_IDX(rangelist, i,
1224251881Speter                                      svn_merge_range_t *);
1225251881Speter                      if (rev > range->start && rev <= range->end)
1226251881Speter                        {
1227251881Speter                          path_is_in_history = TRUE;
1228251881Speter                          break;
1229251881Speter                        }
1230251881Speter                    }
1231251881Speter                }
1232251881Speter              if (path_is_in_history)
1233251881Speter                break;
1234251881Speter            }
1235251881Speter          svn_pool_destroy(inner_subpool);
1236251881Speter
1237251881Speter          if (!path_is_in_history)
1238251881Speter            {
1239251881Speter              /* If even one path in LOG_ENTRY->CHANGED_PATHS2 is not part of
1240251881Speter                 LOG_TARGET_HISTORY_AS_MERGEINFO, then we want to send the
1241251881Speter                 log for REV. */
1242251881Speter              found_rev_of_interest = TRUE;
1243251881Speter              break;
1244251881Speter            }
1245251881Speter        }
1246251881Speter      svn_pool_destroy(subpool);
1247251881Speter    }
1248251881Speter
1249251881Speter  /* If we only got changed paths the sake of detecting redundant merged
1250251881Speter     revisions, then be sure we don't send that info to the receiver. */
1251251881Speter  if (!discover_changed_paths && handling_merged_revision)
1252251881Speter    log_entry->changed_paths = log_entry->changed_paths2 = NULL;
1253251881Speter
1254251881Speter  /* Send the entry to the receiver, unless it is a redundant merged
1255251881Speter     revision. */
1256251881Speter  if (found_rev_of_interest)
1257251881Speter    {
1258251881Speter      /* Is REV a merged revision we've already sent? */
1259251881Speter      if (nested_merges && handling_merged_revision)
1260251881Speter        {
1261251881Speter          svn_revnum_t *merged_rev = apr_hash_get(nested_merges, &rev,
1262251881Speter                                                  sizeof(svn_revnum_t *));
1263251881Speter
1264251881Speter          if (merged_rev)
1265251881Speter            {
1266251881Speter              /* We already sent REV. */
1267251881Speter              return SVN_NO_ERROR;
1268251881Speter            }
1269251881Speter          else
1270251881Speter            {
1271251881Speter              /* NESTED_REVS needs to last across all the send_log, do_logs,
1272251881Speter                 handle_merged_revisions() recursions, so use the pool it
1273251881Speter                 was created in at the top of the recursion. */
1274251881Speter              apr_pool_t *hash_pool = apr_hash_pool_get(nested_merges);
1275251881Speter              svn_revnum_t *long_lived_rev = apr_palloc(hash_pool,
1276251881Speter                                                        sizeof(svn_revnum_t));
1277251881Speter              *long_lived_rev = rev;
1278251881Speter              apr_hash_set(nested_merges, long_lived_rev,
1279251881Speter                           sizeof(svn_revnum_t *), long_lived_rev);
1280251881Speter            }
1281251881Speter        }
1282251881Speter
1283251881Speter      return (*receiver)(receiver_baton, log_entry, pool);
1284251881Speter    }
1285251881Speter  else
1286251881Speter    {
1287251881Speter      return SVN_NO_ERROR;
1288251881Speter    }
1289251881Speter}
1290251881Speter
1291251881Speter/* This controls how many history objects we keep open.  For any targets
1292251881Speter   over this number we have to open and close their histories as needed,
1293251881Speter   which is CPU intensive, but keeps us from using an unbounded amount of
1294251881Speter   memory. */
1295251881Speter#define MAX_OPEN_HISTORIES 32
1296251881Speter
1297251881Speter/* Get the histories for PATHS, and store them in *HISTORIES.
1298251881Speter
1299251881Speter   If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1300251881Speter   repository locations as fatal -- just ignore them.  */
1301251881Speterstatic svn_error_t *
1302251881Speterget_path_histories(apr_array_header_t **histories,
1303251881Speter                   svn_fs_t *fs,
1304251881Speter                   const apr_array_header_t *paths,
1305251881Speter                   svn_revnum_t hist_start,
1306251881Speter                   svn_revnum_t hist_end,
1307251881Speter                   svn_boolean_t strict_node_history,
1308251881Speter                   svn_boolean_t ignore_missing_locations,
1309251881Speter                   svn_repos_authz_func_t authz_read_func,
1310251881Speter                   void *authz_read_baton,
1311251881Speter                   apr_pool_t *pool)
1312251881Speter{
1313251881Speter  svn_fs_root_t *root;
1314251881Speter  apr_pool_t *iterpool;
1315251881Speter  svn_error_t *err;
1316251881Speter  int i;
1317251881Speter
1318251881Speter  /* Create a history object for each path so we can walk through
1319251881Speter     them all at the same time until we have all changes or LIMIT
1320251881Speter     is reached.
1321251881Speter
1322251881Speter     There is some pool fun going on due to the fact that we have
1323251881Speter     to hold on to the old pool with the history before we can
1324251881Speter     get the next history.
1325251881Speter  */
1326251881Speter  *histories = apr_array_make(pool, paths->nelts,
1327251881Speter                              sizeof(struct path_info *));
1328251881Speter
1329251881Speter  SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool));
1330251881Speter
1331251881Speter  iterpool = svn_pool_create(pool);
1332251881Speter  for (i = 0; i < paths->nelts; i++)
1333251881Speter    {
1334251881Speter      const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
1335251881Speter      struct path_info *info = apr_palloc(pool,
1336251881Speter                                          sizeof(struct path_info));
1337251881Speter
1338251881Speter      if (authz_read_func)
1339251881Speter        {
1340251881Speter          svn_boolean_t readable;
1341251881Speter
1342251881Speter          svn_pool_clear(iterpool);
1343251881Speter
1344251881Speter          SVN_ERR(authz_read_func(&readable, root, this_path,
1345251881Speter                                  authz_read_baton, iterpool));
1346251881Speter          if (! readable)
1347251881Speter            return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
1348251881Speter        }
1349251881Speter
1350251881Speter      info->path = svn_stringbuf_create(this_path, pool);
1351251881Speter      info->done = FALSE;
1352251881Speter      info->history_rev = hist_end;
1353251881Speter      info->first_time = TRUE;
1354251881Speter
1355251881Speter      if (i < MAX_OPEN_HISTORIES)
1356251881Speter        {
1357251881Speter          err = svn_fs_node_history(&info->hist, root, this_path, pool);
1358251881Speter          if (err
1359251881Speter              && ignore_missing_locations
1360251881Speter              && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1361251881Speter                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1362251881Speter                  err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1363251881Speter            {
1364251881Speter              svn_error_clear(err);
1365251881Speter              continue;
1366251881Speter            }
1367251881Speter          SVN_ERR(err);
1368251881Speter          info->newpool = svn_pool_create(pool);
1369251881Speter          info->oldpool = svn_pool_create(pool);
1370251881Speter        }
1371251881Speter      else
1372251881Speter        {
1373251881Speter          info->hist = NULL;
1374251881Speter          info->oldpool = NULL;
1375251881Speter          info->newpool = NULL;
1376251881Speter        }
1377251881Speter
1378251881Speter      err = get_history(info, fs,
1379251881Speter                        strict_node_history,
1380251881Speter                        authz_read_func, authz_read_baton,
1381251881Speter                        hist_start, pool);
1382251881Speter      if (err
1383251881Speter          && ignore_missing_locations
1384251881Speter          && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1385251881Speter              err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1386251881Speter              err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1387251881Speter        {
1388251881Speter          svn_error_clear(err);
1389251881Speter          continue;
1390251881Speter        }
1391251881Speter      SVN_ERR(err);
1392251881Speter      APR_ARRAY_PUSH(*histories, struct path_info *) = info;
1393251881Speter    }
1394251881Speter  svn_pool_destroy(iterpool);
1395251881Speter
1396251881Speter  return SVN_NO_ERROR;
1397251881Speter}
1398251881Speter
1399251881Speter/* Remove and return the first item from ARR. */
1400251881Speterstatic void *
1401251881Speterarray_pop_front(apr_array_header_t *arr)
1402251881Speter{
1403251881Speter  void *item = arr->elts;
1404251881Speter
1405251881Speter  if (apr_is_empty_array(arr))
1406251881Speter    return NULL;
1407251881Speter
1408251881Speter  arr->elts += arr->elt_size;
1409251881Speter  arr->nelts -= 1;
1410251881Speter  arr->nalloc -= 1;
1411251881Speter  return item;
1412251881Speter}
1413251881Speter
1414251881Speter/* A struct which represents a single revision range, and the paths which
1415251881Speter   have mergeinfo in that range. */
1416251881Speterstruct path_list_range
1417251881Speter{
1418251881Speter  apr_array_header_t *paths;
1419251881Speter  svn_merge_range_t range;
1420251881Speter
1421251881Speter  /* Is RANGE the result of a reverse merge? */
1422251881Speter  svn_boolean_t reverse_merge;
1423251881Speter};
1424251881Speter
1425251881Speter/* A struct which represents "inverse mergeinfo", that is, instead of having
1426251881Speter   a path->revision_range_list mapping, which is the way mergeinfo is commonly
1427251881Speter   represented, this struct enables a revision_range_list,path tuple, where
1428251881Speter   the paths can be accessed by revision. */
1429251881Speterstruct rangelist_path
1430251881Speter{
1431251881Speter  svn_rangelist_t *rangelist;
1432251881Speter  const char *path;
1433251881Speter};
1434251881Speter
1435251881Speter/* Comparator function for combine_mergeinfo_path_lists().  Sorts
1436251881Speter   rangelist_path structs in increasing order based upon starting revision,
1437251881Speter   then ending revision of the first element in the rangelist.
1438251881Speter
1439251881Speter   This does not sort rangelists based upon subsequent elements, only the
1440251881Speter   first range.  We'll sort any subsequent ranges in the correct order
1441251881Speter   when they get bumped up to the front by removal of earlier ones, so we
1442251881Speter   don't really have to sort them here.  See combine_mergeinfo_path_lists()
1443251881Speter   for details. */
1444251881Speterstatic int
1445251881Spetercompare_rangelist_paths(const void *a, const void *b)
1446251881Speter{
1447251881Speter  struct rangelist_path *rpa = *((struct rangelist_path *const *) a);
1448251881Speter  struct rangelist_path *rpb = *((struct rangelist_path *const *) b);
1449251881Speter  svn_merge_range_t *mra = APR_ARRAY_IDX(rpa->rangelist, 0,
1450251881Speter                                         svn_merge_range_t *);
1451251881Speter  svn_merge_range_t *mrb = APR_ARRAY_IDX(rpb->rangelist, 0,
1452251881Speter                                         svn_merge_range_t *);
1453251881Speter
1454251881Speter  if (mra->start < mrb->start)
1455251881Speter    return -1;
1456251881Speter  if (mra->start > mrb->start)
1457251881Speter    return 1;
1458251881Speter  if (mra->end < mrb->end)
1459251881Speter    return -1;
1460251881Speter  if (mra->end > mrb->end)
1461251881Speter    return 1;
1462251881Speter
1463251881Speter  return 0;
1464251881Speter}
1465251881Speter
1466251881Speter/* From MERGEINFO, return in *COMBINED_LIST, allocated in POOL, a list of
1467251881Speter   'struct path_list_range's.  This list represents the rangelists in
1468251881Speter   MERGEINFO and each path which has mergeinfo in that range.
1469251881Speter   If REVERSE_MERGE is true, then MERGEINFO represents mergeinfo removed
1470251881Speter   as the result of a reverse merge. */
1471251881Speterstatic svn_error_t *
1472251881Spetercombine_mergeinfo_path_lists(apr_array_header_t **combined_list,
1473251881Speter                             svn_mergeinfo_t mergeinfo,
1474251881Speter                             svn_boolean_t reverse_merge,
1475251881Speter                             apr_pool_t *pool)
1476251881Speter{
1477251881Speter  apr_hash_index_t *hi;
1478251881Speter  apr_array_header_t *rangelist_paths;
1479251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
1480251881Speter
1481251881Speter  /* Create a list of (revision range, path) tuples from MERGEINFO. */
1482251881Speter  rangelist_paths = apr_array_make(subpool, apr_hash_count(mergeinfo),
1483251881Speter                                   sizeof(struct rangelist_path *));
1484251881Speter  for (hi = apr_hash_first(subpool, mergeinfo); hi;
1485251881Speter       hi = apr_hash_next(hi))
1486251881Speter    {
1487251881Speter      int i;
1488251881Speter      struct rangelist_path *rp = apr_palloc(subpool, sizeof(*rp));
1489251881Speter      apr_hash_this(hi, (void *) &rp->path, NULL,
1490251881Speter                    (void *) &rp->rangelist);
1491251881Speter      APR_ARRAY_PUSH(rangelist_paths, struct rangelist_path *) = rp;
1492251881Speter
1493251881Speter      /* We need to make local copies of the rangelist, since we will be
1494251881Speter         modifying it, below. */
1495251881Speter      rp->rangelist = svn_rangelist_dup(rp->rangelist, subpool);
1496251881Speter
1497251881Speter      /* Make all of the rangelists inclusive, both start and end. */
1498251881Speter      for (i = 0; i < rp->rangelist->nelts; i++)
1499251881Speter        APR_ARRAY_IDX(rp->rangelist, i, svn_merge_range_t *)->start += 1;
1500251881Speter    }
1501251881Speter
1502251881Speter  /* Loop over the (revision range, path) tuples, chopping them into
1503251881Speter     (revision range, paths) tuples, and appending those to the output
1504251881Speter     list. */
1505251881Speter  if (! *combined_list)
1506251881Speter    *combined_list = apr_array_make(pool, 0, sizeof(struct path_list_range *));
1507251881Speter
1508251881Speter  while (rangelist_paths->nelts > 1)
1509251881Speter    {
1510251881Speter      svn_revnum_t youngest, next_youngest, tail, youngest_end;
1511251881Speter      struct path_list_range *plr;
1512251881Speter      struct rangelist_path *rp;
1513251881Speter      int num_revs;
1514251881Speter      int i;
1515251881Speter
1516251881Speter      /* First, sort the list such that the start revision of the first
1517251881Speter         revision arrays are sorted. */
1518251881Speter      qsort(rangelist_paths->elts, rangelist_paths->nelts,
1519251881Speter            rangelist_paths->elt_size, compare_rangelist_paths);
1520251881Speter
1521251881Speter      /* Next, find the number of revision ranges which start with the same
1522251881Speter         revision. */
1523251881Speter      rp = APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1524251881Speter      youngest =
1525251881Speter        APR_ARRAY_IDX(rp->rangelist, 0, struct svn_merge_range_t *)->start;
1526251881Speter      next_youngest = youngest;
1527251881Speter      for (num_revs = 1; next_youngest == youngest; num_revs++)
1528251881Speter        {
1529251881Speter          if (num_revs == rangelist_paths->nelts)
1530251881Speter            {
1531251881Speter              num_revs += 1;
1532251881Speter              break;
1533251881Speter            }
1534251881Speter          rp = APR_ARRAY_IDX(rangelist_paths, num_revs,
1535251881Speter                             struct rangelist_path *);
1536251881Speter          next_youngest = APR_ARRAY_IDX(rp->rangelist, 0,
1537251881Speter                                        struct svn_merge_range_t *)->start;
1538251881Speter        }
1539251881Speter      num_revs -= 1;
1540251881Speter
1541251881Speter      /* The start of the new range will be YOUNGEST, and we now find the end
1542251881Speter         of the new range, which should be either one less than the next
1543251881Speter         earliest start of a rangelist, or the end of the first rangelist. */
1544251881Speter      youngest_end =
1545251881Speter        APR_ARRAY_IDX(APR_ARRAY_IDX(rangelist_paths, 0,
1546251881Speter                                    struct rangelist_path *)->rangelist,
1547251881Speter                      0, svn_merge_range_t *)->end;
1548251881Speter      if ( (next_youngest == youngest) || (youngest_end < next_youngest) )
1549251881Speter        tail = youngest_end;
1550251881Speter      else
1551251881Speter        tail = next_youngest - 1;
1552251881Speter
1553251881Speter      /* Insert the (earliest, tail) tuple into the output list, along with
1554251881Speter         a list of paths which match it. */
1555251881Speter      plr = apr_palloc(pool, sizeof(*plr));
1556251881Speter      plr->reverse_merge = reverse_merge;
1557251881Speter      plr->range.start = youngest;
1558251881Speter      plr->range.end = tail;
1559251881Speter      plr->paths = apr_array_make(pool, num_revs, sizeof(const char *));
1560251881Speter      for (i = 0; i < num_revs; i++)
1561251881Speter        APR_ARRAY_PUSH(plr->paths, const char *) =
1562251881Speter          APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *)->path;
1563251881Speter      APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1564251881Speter
1565251881Speter      /* Now, check to see which (rangelist path) combinations we can remove,
1566251881Speter         and do so. */
1567251881Speter      for (i = 0; i < num_revs; i++)
1568251881Speter        {
1569251881Speter          svn_merge_range_t *range;
1570251881Speter          rp = APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *);
1571251881Speter          range = APR_ARRAY_IDX(rp->rangelist, 0, svn_merge_range_t *);
1572251881Speter
1573251881Speter          /* Set the start of the range to beyond the end of the range we
1574251881Speter             just built.  If the range is now "inverted", we can get pop it
1575251881Speter             off the list. */
1576251881Speter          range->start = tail + 1;
1577251881Speter          if (range->start > range->end)
1578251881Speter            {
1579251881Speter              if (rp->rangelist->nelts == 1)
1580251881Speter                {
1581251881Speter                  /* The range is the only on its list, so we should remove
1582251881Speter                     the entire rangelist_path, adjusting our loop control
1583251881Speter                     variables appropriately. */
1584251881Speter                  array_pop_front(rangelist_paths);
1585251881Speter                  i--;
1586251881Speter                  num_revs--;
1587251881Speter                }
1588251881Speter              else
1589251881Speter                {
1590251881Speter                  /* We have more than one range on the list, so just remove
1591251881Speter                     the first one. */
1592251881Speter                  array_pop_front(rp->rangelist);
1593251881Speter                }
1594251881Speter            }
1595251881Speter        }
1596251881Speter    }
1597251881Speter
1598251881Speter  /* Finally, add the last remaining (revision range, path) to the output
1599251881Speter     list. */
1600251881Speter  if (rangelist_paths->nelts > 0)
1601251881Speter    {
1602251881Speter      struct rangelist_path *first_rp =
1603251881Speter        APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1604251881Speter      while (first_rp->rangelist->nelts > 0)
1605251881Speter        {
1606251881Speter          struct path_list_range *plr = apr_palloc(pool, sizeof(*plr));
1607251881Speter
1608251881Speter          plr->reverse_merge = reverse_merge;
1609251881Speter          plr->paths = apr_array_make(pool, 1, sizeof(const char *));
1610251881Speter          APR_ARRAY_PUSH(plr->paths, const char *) = first_rp->path;
1611251881Speter          plr->range = *APR_ARRAY_IDX(first_rp->rangelist, 0,
1612251881Speter                                      svn_merge_range_t *);
1613251881Speter          array_pop_front(first_rp->rangelist);
1614251881Speter          APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1615251881Speter        }
1616251881Speter    }
1617251881Speter
1618251881Speter  svn_pool_destroy(subpool);
1619251881Speter
1620251881Speter  return SVN_NO_ERROR;
1621251881Speter}
1622251881Speter
1623251881Speter
1624251881Speter/* Pity that C is so ... linear. */
1625251881Speterstatic svn_error_t *
1626251881Speterdo_logs(svn_fs_t *fs,
1627251881Speter        const apr_array_header_t *paths,
1628251881Speter        svn_mergeinfo_t log_target_history_as_mergeinfo,
1629251881Speter        svn_mergeinfo_t processed,
1630251881Speter        apr_hash_t *nested_merges,
1631251881Speter        svn_revnum_t hist_start,
1632251881Speter        svn_revnum_t hist_end,
1633251881Speter        int limit,
1634251881Speter        svn_boolean_t discover_changed_paths,
1635251881Speter        svn_boolean_t strict_node_history,
1636251881Speter        svn_boolean_t include_merged_revisions,
1637251881Speter        svn_boolean_t handling_merged_revisions,
1638251881Speter        svn_boolean_t subtractive_merge,
1639251881Speter        svn_boolean_t ignore_missing_locations,
1640251881Speter        const apr_array_header_t *revprops,
1641251881Speter        svn_boolean_t descending_order,
1642251881Speter        svn_log_entry_receiver_t receiver,
1643251881Speter        void *receiver_baton,
1644251881Speter        svn_repos_authz_func_t authz_read_func,
1645251881Speter        void *authz_read_baton,
1646251881Speter        apr_pool_t *pool);
1647251881Speter
1648251881Speter/* Comparator function for handle_merged_revisions().  Sorts path_list_range
1649251881Speter   structs in increasing order based on the struct's RANGE.START revision,
1650251881Speter   then RANGE.END revision. */
1651251881Speterstatic int
1652251881Spetercompare_path_list_range(const void *a, const void *b)
1653251881Speter{
1654251881Speter  struct path_list_range *plr_a = *((struct path_list_range *const *) a);
1655251881Speter  struct path_list_range *plr_b = *((struct path_list_range *const *) b);
1656251881Speter
1657251881Speter  if (plr_a->range.start < plr_b->range.start)
1658251881Speter    return -1;
1659251881Speter  if (plr_a->range.start > plr_b->range.start)
1660251881Speter    return 1;
1661251881Speter  if (plr_a->range.end < plr_b->range.end)
1662251881Speter    return -1;
1663251881Speter  if (plr_a->range.end > plr_b->range.end)
1664251881Speter    return 1;
1665251881Speter
1666251881Speter  return 0;
1667251881Speter}
1668251881Speter
1669251881Speter/* Examine the ADDED_MERGEINFO and DELETED_MERGEINFO for revision REV in FS
1670251881Speter   (as collected by examining paths of interest to a log operation), and
1671251881Speter   determine which revisions to report as having been merged or reverse-merged
1672251881Speter   via the commit resulting in REV.
1673251881Speter
1674251881Speter   Silently ignore some failures to find the revisions mentioned in the
1675251881Speter   added/deleted mergeinfos, as might happen if there is invalid mergeinfo.
1676251881Speter
1677251881Speter   Other parameters are as described by do_logs(), around which this
1678251881Speter   is a recursion wrapper. */
1679251881Speterstatic svn_error_t *
1680251881Speterhandle_merged_revisions(svn_revnum_t rev,
1681251881Speter                        svn_fs_t *fs,
1682251881Speter                        svn_mergeinfo_t log_target_history_as_mergeinfo,
1683251881Speter                        apr_hash_t *nested_merges,
1684251881Speter                        svn_mergeinfo_t processed,
1685251881Speter                        svn_mergeinfo_t added_mergeinfo,
1686251881Speter                        svn_mergeinfo_t deleted_mergeinfo,
1687251881Speter                        svn_boolean_t discover_changed_paths,
1688251881Speter                        svn_boolean_t strict_node_history,
1689251881Speter                        const apr_array_header_t *revprops,
1690251881Speter                        svn_log_entry_receiver_t receiver,
1691251881Speter                        void *receiver_baton,
1692251881Speter                        svn_repos_authz_func_t authz_read_func,
1693251881Speter                        void *authz_read_baton,
1694251881Speter                        apr_pool_t *pool)
1695251881Speter{
1696251881Speter  apr_array_header_t *combined_list = NULL;
1697251881Speter  svn_log_entry_t *empty_log_entry;
1698251881Speter  apr_pool_t *iterpool;
1699251881Speter  int i;
1700251881Speter
1701251881Speter  if (apr_hash_count(added_mergeinfo) == 0
1702251881Speter      && apr_hash_count(deleted_mergeinfo) == 0)
1703251881Speter    return SVN_NO_ERROR;
1704251881Speter
1705251881Speter  if (apr_hash_count(added_mergeinfo))
1706251881Speter    SVN_ERR(combine_mergeinfo_path_lists(&combined_list, added_mergeinfo,
1707251881Speter                                          FALSE, pool));
1708251881Speter
1709251881Speter  if (apr_hash_count(deleted_mergeinfo))
1710251881Speter    SVN_ERR(combine_mergeinfo_path_lists(&combined_list, deleted_mergeinfo,
1711251881Speter                                          TRUE, pool));
1712251881Speter
1713251881Speter  SVN_ERR_ASSERT(combined_list != NULL);
1714251881Speter  qsort(combined_list->elts, combined_list->nelts,
1715251881Speter        combined_list->elt_size, compare_path_list_range);
1716251881Speter
1717251881Speter  /* Because the combined_lists are ordered youngest to oldest,
1718251881Speter     iterate over them in reverse. */
1719251881Speter  iterpool = svn_pool_create(pool);
1720251881Speter  for (i = combined_list->nelts - 1; i >= 0; i--)
1721251881Speter    {
1722251881Speter      struct path_list_range *pl_range
1723251881Speter        = APR_ARRAY_IDX(combined_list, i, struct path_list_range *);
1724251881Speter
1725251881Speter      svn_pool_clear(iterpool);
1726251881Speter      SVN_ERR(do_logs(fs, pl_range->paths, log_target_history_as_mergeinfo,
1727251881Speter                      processed, nested_merges,
1728251881Speter                      pl_range->range.start, pl_range->range.end, 0,
1729251881Speter                      discover_changed_paths, strict_node_history,
1730251881Speter                      TRUE, pl_range->reverse_merge, TRUE, TRUE,
1731251881Speter                      revprops, TRUE, receiver, receiver_baton,
1732251881Speter                      authz_read_func, authz_read_baton, iterpool));
1733251881Speter    }
1734251881Speter  svn_pool_destroy(iterpool);
1735251881Speter
1736251881Speter  /* Send the empty revision.  */
1737251881Speter  empty_log_entry = svn_log_entry_create(pool);
1738251881Speter  empty_log_entry->revision = SVN_INVALID_REVNUM;
1739251881Speter  return (*receiver)(receiver_baton, empty_log_entry, pool);
1740251881Speter}
1741251881Speter
1742251881Speter/* This is used by do_logs to differentiate between forward and
1743251881Speter   reverse merges. */
1744251881Speterstruct added_deleted_mergeinfo
1745251881Speter{
1746251881Speter  svn_mergeinfo_t added_mergeinfo;
1747251881Speter  svn_mergeinfo_t deleted_mergeinfo;
1748251881Speter};
1749251881Speter
1750251881Speter/* Reduce the search range PATHS, HIST_START, HIST_END by removing
1751251881Speter   parts already covered by PROCESSED.  If reduction is possible
1752251881Speter   elements may be removed from PATHS and *START_REDUCED and
1753251881Speter   *END_REDUCED may be set to a narrower range. */
1754251881Speterstatic svn_error_t *
1755251881Speterreduce_search(apr_array_header_t *paths,
1756251881Speter              svn_revnum_t *hist_start,
1757251881Speter              svn_revnum_t *hist_end,
1758251881Speter              svn_mergeinfo_t processed,
1759251881Speter              apr_pool_t *scratch_pool)
1760251881Speter{
1761251881Speter  /* We add 1 to end to compensate for store_search */
1762251881Speter  svn_revnum_t start = *hist_start <= *hist_end ? *hist_start : *hist_end;
1763251881Speter  svn_revnum_t end = *hist_start <= *hist_end ? *hist_end + 1 : *hist_start + 1;
1764251881Speter  int i;
1765251881Speter
1766251881Speter  for (i = 0; i < paths->nelts; ++i)
1767251881Speter    {
1768251881Speter      const char *path = APR_ARRAY_IDX(paths, i, const char *);
1769251881Speter      svn_rangelist_t *ranges = svn_hash_gets(processed, path);
1770251881Speter      int j;
1771251881Speter
1772251881Speter      if (!ranges)
1773251881Speter        continue;
1774251881Speter
1775251881Speter      /* ranges is ordered, could we use some sort of binary search
1776251881Speter         rather than iterating? */
1777251881Speter      for (j = 0; j < ranges->nelts; ++j)
1778251881Speter        {
1779251881Speter          svn_merge_range_t *range = APR_ARRAY_IDX(ranges, j,
1780251881Speter                                                   svn_merge_range_t *);
1781251881Speter          if (range->start <= start && range->end >= end)
1782251881Speter            {
1783251881Speter              for (j = i; j < paths->nelts - 1; ++j)
1784251881Speter                APR_ARRAY_IDX(paths, j, const char *)
1785251881Speter                  = APR_ARRAY_IDX(paths, j + 1, const char *);
1786251881Speter
1787251881Speter              --paths->nelts;
1788251881Speter              --i;
1789251881Speter              break;
1790251881Speter            }
1791251881Speter
1792251881Speter          /* If there is only one path then we also check for a
1793251881Speter             partial overlap rather than the full overlap above, and
1794251881Speter             reduce the [hist_start, hist_end] range rather than
1795251881Speter             dropping the path. */
1796251881Speter          if (paths->nelts == 1)
1797251881Speter            {
1798251881Speter              if (range->start <= start && range->end > start)
1799251881Speter                {
1800251881Speter                  if (start == *hist_start)
1801251881Speter                    *hist_start = range->end - 1;
1802251881Speter                  else
1803251881Speter                    *hist_end = range->end - 1;
1804251881Speter                  break;
1805251881Speter                }
1806251881Speter              if (range->start < end && range->end >= end)
1807251881Speter                {
1808251881Speter                  if (start == *hist_start)
1809251881Speter                    *hist_end = range->start;
1810251881Speter                  else
1811251881Speter                    *hist_start = range->start;
1812251881Speter                  break;
1813251881Speter                }
1814251881Speter            }
1815251881Speter        }
1816251881Speter    }
1817251881Speter
1818251881Speter  return SVN_NO_ERROR;
1819251881Speter}
1820251881Speter
1821251881Speter/* Extend PROCESSED to cover PATHS from HIST_START to HIST_END */
1822251881Speterstatic svn_error_t *
1823251881Speterstore_search(svn_mergeinfo_t processed,
1824251881Speter             const apr_array_header_t *paths,
1825251881Speter             svn_revnum_t hist_start,
1826251881Speter             svn_revnum_t hist_end,
1827251881Speter             apr_pool_t *scratch_pool)
1828251881Speter{
1829251881Speter  /* We add 1 to end so that we can use the mergeinfo API to handle
1830251881Speter     singe revisions where HIST_START is equal to HIST_END. */
1831251881Speter  svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end;
1832251881Speter  svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1;
1833251881Speter  svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool);
1834251881Speter  apr_pool_t *processed_pool = apr_hash_pool_get(processed);
1835251881Speter  int i;
1836251881Speter
1837251881Speter  for (i = 0; i < paths->nelts; ++i)
1838251881Speter    {
1839251881Speter      const char *path = APR_ARRAY_IDX(paths, i, const char *);
1840251881Speter      svn_rangelist_t *ranges = apr_array_make(processed_pool, 1,
1841251881Speter                                               sizeof(svn_merge_range_t*));
1842251881Speter      svn_merge_range_t *range = apr_palloc(processed_pool,
1843251881Speter                                            sizeof(svn_merge_range_t));
1844251881Speter
1845251881Speter      range->start = start;
1846251881Speter      range->end = end;
1847251881Speter      range->inheritable = TRUE;
1848251881Speter      APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range;
1849251881Speter      svn_hash_sets(mergeinfo, apr_pstrdup(processed_pool, path), ranges);
1850251881Speter    }
1851251881Speter  SVN_ERR(svn_mergeinfo_merge2(processed, mergeinfo,
1852251881Speter                               apr_hash_pool_get(processed), scratch_pool));
1853251881Speter
1854251881Speter  return SVN_NO_ERROR;
1855251881Speter}
1856251881Speter
1857251881Speter/* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke
1858251881Speter   RECEIVER with RECEIVER_BATON on them.  If DESCENDING_ORDER is TRUE, send
1859251881Speter   the logs back as we find them, else buffer the logs and send them back
1860251881Speter   in youngest->oldest order.
1861251881Speter
1862251881Speter   If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1863251881Speter   repository locations as fatal -- just ignore them.
1864251881Speter
1865251881Speter   If LOG_TARGET_HISTORY_AS_MERGEINFO is not NULL then it contains mergeinfo
1866251881Speter   representing the history of PATHS between HIST_START and HIST_END.
1867251881Speter
1868251881Speter   If HANDLING_MERGED_REVISIONS is TRUE then this is a recursive call for
1869251881Speter   merged revisions, see INCLUDE_MERGED_REVISIONS argument to
1870251881Speter   svn_repos_get_logs4().  If SUBTRACTIVE_MERGE is true, then this is a
1871251881Speter   recursive call for reverse merged revisions.
1872251881Speter
1873251881Speter   If NESTED_MERGES is not NULL then it is a hash of revisions (svn_revnum_t *
1874251881Speter   mapped to svn_revnum_t *) for logs that were previously sent.  On the first
1875251881Speter   call to do_logs it should always be NULL.  If INCLUDE_MERGED_REVISIONS is
1876251881Speter   TRUE, then NESTED_MERGES will be created on the first call to do_logs,
1877251881Speter   allocated in POOL.  It is then shared across
1878251881Speter   do_logs()/send_logs()/handle_merge_revisions() recursions, see also the
1879251881Speter   argument of the same name in send_logs().
1880251881Speter
1881251881Speter   PROCESSED is a mergeinfo hash that represents the paths and
1882251881Speter   revisions that have already been searched.  Allocated like
1883251881Speter   NESTED_MERGES above.
1884251881Speter
1885251881Speter   All other parameters are the same as svn_repos_get_logs4().
1886251881Speter */
1887251881Speterstatic svn_error_t *
1888251881Speterdo_logs(svn_fs_t *fs,
1889251881Speter        const apr_array_header_t *paths,
1890251881Speter        svn_mergeinfo_t log_target_history_as_mergeinfo,
1891251881Speter        svn_mergeinfo_t processed,
1892251881Speter        apr_hash_t *nested_merges,
1893251881Speter        svn_revnum_t hist_start,
1894251881Speter        svn_revnum_t hist_end,
1895251881Speter        int limit,
1896251881Speter        svn_boolean_t discover_changed_paths,
1897251881Speter        svn_boolean_t strict_node_history,
1898251881Speter        svn_boolean_t include_merged_revisions,
1899251881Speter        svn_boolean_t subtractive_merge,
1900251881Speter        svn_boolean_t handling_merged_revisions,
1901251881Speter        svn_boolean_t ignore_missing_locations,
1902251881Speter        const apr_array_header_t *revprops,
1903251881Speter        svn_boolean_t descending_order,
1904251881Speter        svn_log_entry_receiver_t receiver,
1905251881Speter        void *receiver_baton,
1906251881Speter        svn_repos_authz_func_t authz_read_func,
1907251881Speter        void *authz_read_baton,
1908251881Speter        apr_pool_t *pool)
1909251881Speter{
1910251881Speter  apr_pool_t *iterpool;
1911251881Speter  apr_pool_t *subpool = NULL;
1912251881Speter  apr_array_header_t *revs = NULL;
1913251881Speter  apr_hash_t *rev_mergeinfo = NULL;
1914251881Speter  svn_revnum_t current;
1915251881Speter  apr_array_header_t *histories;
1916251881Speter  svn_boolean_t any_histories_left = TRUE;
1917251881Speter  int send_count = 0;
1918251881Speter  int i;
1919251881Speter
1920251881Speter  if (processed)
1921251881Speter    {
1922251881Speter      /* Casting away const. This only happens on recursive calls when
1923251881Speter         it is known to be safe because we allocated paths. */
1924251881Speter      SVN_ERR(reduce_search((apr_array_header_t *)paths, &hist_start, &hist_end,
1925251881Speter                            processed, pool));
1926251881Speter    }
1927251881Speter
1928251881Speter  if (!paths->nelts)
1929251881Speter    return SVN_NO_ERROR;
1930251881Speter
1931251881Speter  if (processed)
1932251881Speter    SVN_ERR(store_search(processed, paths, hist_start, hist_end, pool));
1933251881Speter
1934251881Speter  /* We have a list of paths and a revision range.  But we don't care
1935251881Speter     about all the revisions in the range -- only the ones in which
1936251881Speter     one of our paths was changed.  So let's go figure out which
1937251881Speter     revisions contain real changes to at least one of our paths.  */
1938251881Speter  SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end,
1939251881Speter                             strict_node_history, ignore_missing_locations,
1940251881Speter                             authz_read_func, authz_read_baton, pool));
1941251881Speter
1942251881Speter  /* Loop through all the revisions in the range and add any
1943251881Speter     where a path was changed to the array, or if they wanted
1944251881Speter     history in reverse order just send it to them right away. */
1945251881Speter  iterpool = svn_pool_create(pool);
1946251881Speter  for (current = hist_end;
1947251881Speter       any_histories_left;
1948251881Speter       current = next_history_rev(histories))
1949251881Speter    {
1950251881Speter      svn_boolean_t changed = FALSE;
1951251881Speter      any_histories_left = FALSE;
1952251881Speter      svn_pool_clear(iterpool);
1953251881Speter
1954251881Speter      for (i = 0; i < histories->nelts; i++)
1955251881Speter        {
1956251881Speter          struct path_info *info = APR_ARRAY_IDX(histories, i,
1957251881Speter                                                 struct path_info *);
1958251881Speter
1959251881Speter          /* Check history for this path in current rev. */
1960251881Speter          SVN_ERR(check_history(&changed, info, fs, current,
1961251881Speter                                strict_node_history, authz_read_func,
1962251881Speter                                authz_read_baton, hist_start, pool));
1963251881Speter          if (! info->done)
1964251881Speter            any_histories_left = TRUE;
1965251881Speter        }
1966251881Speter
1967251881Speter      /* If any of the paths changed in this rev then add or send it. */
1968251881Speter      if (changed)
1969251881Speter        {
1970251881Speter          svn_mergeinfo_t added_mergeinfo = NULL;
1971251881Speter          svn_mergeinfo_t deleted_mergeinfo = NULL;
1972251881Speter          svn_boolean_t has_children = FALSE;
1973251881Speter          apr_hash_t *changes = NULL;
1974251881Speter
1975251881Speter          /* If we're including merged revisions, we need to calculate
1976251881Speter             the mergeinfo deltas committed in this revision to our
1977251881Speter             various paths. */
1978251881Speter          if (include_merged_revisions)
1979251881Speter            {
1980251881Speter              apr_array_header_t *cur_paths =
1981251881Speter                apr_array_make(iterpool, paths->nelts, sizeof(const char *));
1982251881Speter
1983251881Speter              /* Get the current paths of our history objects so we can
1984251881Speter                 query mergeinfo. */
1985251881Speter              /* ### TODO: Should this be ignoring depleted history items? */
1986251881Speter              for (i = 0; i < histories->nelts; i++)
1987251881Speter                {
1988251881Speter                  struct path_info *info = APR_ARRAY_IDX(histories, i,
1989251881Speter                                                         struct path_info *);
1990251881Speter                  APR_ARRAY_PUSH(cur_paths, const char *) = info->path->data;
1991251881Speter                }
1992251881Speter              SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo,
1993251881Speter                                                     &deleted_mergeinfo,
1994251881Speter                                                     &changes,
1995251881Speter                                                     fs, cur_paths,
1996251881Speter                                                     current, iterpool,
1997251881Speter                                                     iterpool));
1998251881Speter              has_children = (apr_hash_count(added_mergeinfo) > 0
1999251881Speter                              || apr_hash_count(deleted_mergeinfo) > 0);
2000251881Speter            }
2001251881Speter
2002251881Speter          /* If our caller wants logs in descending order, we can send
2003251881Speter             'em now (because that's the order we're crawling history
2004251881Speter             in anyway). */
2005251881Speter          if (descending_order)
2006251881Speter            {
2007251881Speter              SVN_ERR(send_log(current, fs, changes,
2008251881Speter                               log_target_history_as_mergeinfo, nested_merges,
2009251881Speter                               discover_changed_paths,
2010251881Speter                               subtractive_merge, handling_merged_revisions,
2011251881Speter                               revprops, has_children,
2012251881Speter                               receiver, receiver_baton,
2013251881Speter                               authz_read_func, authz_read_baton, iterpool));
2014251881Speter
2015251881Speter              if (has_children) /* Implies include_merged_revisions == TRUE */
2016251881Speter                {
2017251881Speter                  if (!nested_merges)
2018251881Speter                    {
2019251881Speter                      /* We're at the start of the recursion stack, create a
2020251881Speter                         single hash to be shared across all of the merged
2021251881Speter                         recursions so we can track and squelch duplicates. */
2022251881Speter                      subpool = svn_pool_create(pool);
2023251881Speter                      nested_merges = svn_hash__make(subpool);
2024251881Speter                      processed = svn_hash__make(subpool);
2025251881Speter                    }
2026251881Speter
2027251881Speter                  SVN_ERR(handle_merged_revisions(
2028251881Speter                    current, fs,
2029251881Speter                    log_target_history_as_mergeinfo, nested_merges,
2030251881Speter                    processed,
2031251881Speter                    added_mergeinfo, deleted_mergeinfo,
2032251881Speter                    discover_changed_paths,
2033251881Speter                    strict_node_history,
2034251881Speter                    revprops,
2035251881Speter                    receiver, receiver_baton,
2036251881Speter                    authz_read_func,
2037251881Speter                    authz_read_baton,
2038251881Speter                    iterpool));
2039251881Speter                }
2040251881Speter              if (limit && ++send_count >= limit)
2041251881Speter                break;
2042251881Speter            }
2043251881Speter          /* Otherwise, the caller wanted logs in ascending order, so
2044251881Speter             we have to buffer up a list of revs and (if doing
2045251881Speter             mergeinfo) a hash of related mergeinfo deltas, and
2046251881Speter             process them later. */
2047251881Speter          else
2048251881Speter            {
2049251881Speter              if (! revs)
2050251881Speter                revs = apr_array_make(pool, 64, sizeof(svn_revnum_t));
2051251881Speter              APR_ARRAY_PUSH(revs, svn_revnum_t) = current;
2052251881Speter
2053251881Speter              if (added_mergeinfo || deleted_mergeinfo)
2054251881Speter                {
2055251881Speter                  svn_revnum_t *cur_rev = apr_pcalloc(pool, sizeof(*cur_rev));
2056251881Speter                  struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2057251881Speter                    apr_palloc(pool, sizeof(*add_and_del_mergeinfo));
2058251881Speter
2059251881Speter                  if (added_mergeinfo)
2060251881Speter                    add_and_del_mergeinfo->added_mergeinfo =
2061251881Speter                      svn_mergeinfo_dup(added_mergeinfo, pool);
2062251881Speter
2063251881Speter                  if (deleted_mergeinfo)
2064251881Speter                    add_and_del_mergeinfo->deleted_mergeinfo =
2065251881Speter                      svn_mergeinfo_dup(deleted_mergeinfo, pool);
2066251881Speter
2067251881Speter                  *cur_rev = current;
2068251881Speter                  if (! rev_mergeinfo)
2069251881Speter                    rev_mergeinfo = svn_hash__make(pool);
2070251881Speter                  apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev),
2071251881Speter                               add_and_del_mergeinfo);
2072251881Speter                }
2073251881Speter            }
2074251881Speter        }
2075251881Speter    }
2076251881Speter  svn_pool_destroy(iterpool);
2077251881Speter
2078251881Speter  if (subpool)
2079251881Speter    {
2080251881Speter      nested_merges = NULL;
2081251881Speter      svn_pool_destroy(subpool);
2082251881Speter    }
2083251881Speter
2084251881Speter  if (revs)
2085251881Speter    {
2086251881Speter      /* Work loop for processing the revisions we found since they wanted
2087251881Speter         history in forward order. */
2088251881Speter      iterpool = svn_pool_create(pool);
2089251881Speter      for (i = 0; i < revs->nelts; ++i)
2090251881Speter        {
2091251881Speter          svn_mergeinfo_t added_mergeinfo;
2092251881Speter          svn_mergeinfo_t deleted_mergeinfo;
2093251881Speter          svn_boolean_t has_children = FALSE;
2094251881Speter
2095251881Speter          svn_pool_clear(iterpool);
2096251881Speter          current = APR_ARRAY_IDX(revs, revs->nelts - i - 1, svn_revnum_t);
2097251881Speter
2098251881Speter          /* If we've got a hash of revision mergeinfo (which can only
2099251881Speter             happen if INCLUDE_MERGED_REVISIONS was set), we check to
2100251881Speter             see if this revision is one which merged in other
2101251881Speter             revisions we need to handle recursively. */
2102251881Speter          if (rev_mergeinfo)
2103251881Speter            {
2104251881Speter              struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2105251881Speter                apr_hash_get(rev_mergeinfo, &current, sizeof(svn_revnum_t));
2106251881Speter              added_mergeinfo = add_and_del_mergeinfo->added_mergeinfo;
2107251881Speter              deleted_mergeinfo = add_and_del_mergeinfo->deleted_mergeinfo;
2108251881Speter              has_children = (apr_hash_count(added_mergeinfo) > 0
2109251881Speter                              || apr_hash_count(deleted_mergeinfo) > 0);
2110251881Speter            }
2111251881Speter
2112251881Speter          SVN_ERR(send_log(current, fs, NULL,
2113251881Speter                           log_target_history_as_mergeinfo, nested_merges,
2114251881Speter                           discover_changed_paths, subtractive_merge,
2115251881Speter                           handling_merged_revisions, revprops, has_children,
2116251881Speter                           receiver, receiver_baton, authz_read_func,
2117251881Speter                           authz_read_baton, iterpool));
2118251881Speter          if (has_children)
2119251881Speter            {
2120251881Speter              if (!nested_merges)
2121251881Speter                {
2122251881Speter                  subpool = svn_pool_create(pool);
2123251881Speter                  nested_merges = svn_hash__make(subpool);
2124251881Speter                }
2125251881Speter
2126251881Speter              SVN_ERR(handle_merged_revisions(current, fs,
2127251881Speter                                              log_target_history_as_mergeinfo,
2128251881Speter                                              nested_merges,
2129251881Speter                                              processed,
2130251881Speter                                              added_mergeinfo,
2131251881Speter                                              deleted_mergeinfo,
2132251881Speter                                              discover_changed_paths,
2133251881Speter                                              strict_node_history, revprops,
2134251881Speter                                              receiver, receiver_baton,
2135251881Speter                                              authz_read_func,
2136251881Speter                                              authz_read_baton,
2137251881Speter                                              iterpool));
2138251881Speter            }
2139251881Speter          if (limit && i + 1 >= limit)
2140251881Speter            break;
2141251881Speter        }
2142251881Speter      svn_pool_destroy(iterpool);
2143251881Speter    }
2144251881Speter
2145251881Speter  return SVN_NO_ERROR;
2146251881Speter}
2147251881Speter
2148251881Speterstruct location_segment_baton
2149251881Speter{
2150251881Speter  apr_array_header_t *history_segments;
2151251881Speter  apr_pool_t *pool;
2152251881Speter};
2153251881Speter
2154251881Speter/* svn_location_segment_receiver_t implementation for svn_repos_get_logs4. */
2155251881Speterstatic svn_error_t *
2156251881Speterlocation_segment_receiver(svn_location_segment_t *segment,
2157251881Speter                          void *baton,
2158251881Speter                          apr_pool_t *pool)
2159251881Speter{
2160251881Speter  struct location_segment_baton *b = baton;
2161251881Speter
2162251881Speter  APR_ARRAY_PUSH(b->history_segments, svn_location_segment_t *) =
2163251881Speter    svn_location_segment_dup(segment, b->pool);
2164251881Speter
2165251881Speter  return SVN_NO_ERROR;
2166251881Speter}
2167251881Speter
2168251881Speter
2169251881Speter/* Populate *PATHS_HISTORY_MERGEINFO with mergeinfo representing the combined
2170251881Speter   history of each path in PATHS between START_REV and END_REV in REPOS's
2171251881Speter   filesystem.  START_REV and END_REV must be valid revisions.  RESULT_POOL
2172251881Speter   is used to allocate *PATHS_HISTORY_MERGEINFO, SCRATCH_POOL is used for all
2173251881Speter   other (temporary) allocations.  Other parameters are the same as
2174251881Speter   svn_repos_get_logs4(). */
2175251881Speterstatic svn_error_t *
2176251881Speterget_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo,
2177251881Speter                               svn_repos_t *repos,
2178251881Speter                               const apr_array_header_t *paths,
2179251881Speter                               svn_revnum_t start_rev,
2180251881Speter                               svn_revnum_t end_rev,
2181251881Speter                               svn_repos_authz_func_t authz_read_func,
2182251881Speter                               void *authz_read_baton,
2183251881Speter                               apr_pool_t *result_pool,
2184251881Speter                               apr_pool_t *scratch_pool)
2185251881Speter{
2186251881Speter  int i;
2187251881Speter  svn_mergeinfo_t path_history_mergeinfo;
2188251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2189251881Speter
2190251881Speter  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_rev));
2191251881Speter  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_rev));
2192251881Speter
2193251881Speter  /* Ensure START_REV is the youngest revision, as required by
2194251881Speter     svn_repos_node_location_segments, for which this is an iterative
2195251881Speter     wrapper. */
2196251881Speter  if (start_rev < end_rev)
2197251881Speter    {
2198251881Speter      svn_revnum_t tmp_rev = start_rev;
2199251881Speter      start_rev = end_rev;
2200251881Speter      end_rev = tmp_rev;
2201251881Speter    }
2202251881Speter
2203251881Speter  *paths_history_mergeinfo = svn_hash__make(result_pool);
2204251881Speter
2205251881Speter  for (i = 0; i < paths->nelts; i++)
2206251881Speter    {
2207251881Speter      const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
2208251881Speter      struct location_segment_baton loc_seg_baton;
2209251881Speter
2210251881Speter      svn_pool_clear(iterpool);
2211251881Speter      loc_seg_baton.pool = scratch_pool;
2212251881Speter      loc_seg_baton.history_segments =
2213251881Speter        apr_array_make(iterpool, 4, sizeof(svn_location_segment_t *));
2214251881Speter
2215251881Speter      SVN_ERR(svn_repos_node_location_segments(repos, this_path, start_rev,
2216251881Speter                                               start_rev, end_rev,
2217251881Speter                                               location_segment_receiver,
2218251881Speter                                               &loc_seg_baton,
2219251881Speter                                               authz_read_func,
2220251881Speter                                               authz_read_baton,
2221251881Speter                                               iterpool));
2222251881Speter
2223251881Speter      SVN_ERR(svn_mergeinfo__mergeinfo_from_segments(
2224251881Speter        &path_history_mergeinfo, loc_seg_baton.history_segments, iterpool));
2225251881Speter      SVN_ERR(svn_mergeinfo_merge2(*paths_history_mergeinfo,
2226251881Speter                                   svn_mergeinfo_dup(path_history_mergeinfo,
2227251881Speter                                                     result_pool),
2228251881Speter                                   result_pool, iterpool));
2229251881Speter    }
2230251881Speter  svn_pool_destroy(iterpool);
2231251881Speter  return SVN_NO_ERROR;
2232251881Speter}
2233251881Speter
2234251881Spetersvn_error_t *
2235251881Spetersvn_repos_get_logs4(svn_repos_t *repos,
2236251881Speter                    const apr_array_header_t *paths,
2237251881Speter                    svn_revnum_t start,
2238251881Speter                    svn_revnum_t end,
2239251881Speter                    int limit,
2240251881Speter                    svn_boolean_t discover_changed_paths,
2241251881Speter                    svn_boolean_t strict_node_history,
2242251881Speter                    svn_boolean_t include_merged_revisions,
2243251881Speter                    const apr_array_header_t *revprops,
2244251881Speter                    svn_repos_authz_func_t authz_read_func,
2245251881Speter                    void *authz_read_baton,
2246251881Speter                    svn_log_entry_receiver_t receiver,
2247251881Speter                    void *receiver_baton,
2248251881Speter                    apr_pool_t *pool)
2249251881Speter{
2250251881Speter  svn_revnum_t head = SVN_INVALID_REVNUM;
2251251881Speter  svn_fs_t *fs = repos->fs;
2252251881Speter  svn_boolean_t descending_order;
2253251881Speter  svn_mergeinfo_t paths_history_mergeinfo = NULL;
2254251881Speter
2255251881Speter  /* Setup log range. */
2256251881Speter  SVN_ERR(svn_fs_youngest_rev(&head, fs, pool));
2257251881Speter
2258251881Speter  if (! SVN_IS_VALID_REVNUM(start))
2259251881Speter    start = head;
2260251881Speter
2261251881Speter  if (! SVN_IS_VALID_REVNUM(end))
2262251881Speter    end = head;
2263251881Speter
2264251881Speter  /* Check that revisions are sane before ever invoking receiver. */
2265251881Speter  if (start > head)
2266251881Speter    return svn_error_createf
2267251881Speter      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2268251881Speter       _("No such revision %ld"), start);
2269251881Speter  if (end > head)
2270251881Speter    return svn_error_createf
2271251881Speter      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2272251881Speter       _("No such revision %ld"), end);
2273251881Speter
2274251881Speter  /* Ensure a youngest-to-oldest revision crawl ordering using our
2275251881Speter     (possibly sanitized) range values. */
2276251881Speter  descending_order = start >= end;
2277251881Speter  if (descending_order)
2278251881Speter    {
2279251881Speter      svn_revnum_t tmp_rev = start;
2280251881Speter      start = end;
2281251881Speter      end = tmp_rev;
2282251881Speter    }
2283251881Speter
2284251881Speter  if (! paths)
2285251881Speter    paths = apr_array_make(pool, 0, sizeof(const char *));
2286251881Speter
2287251881Speter  /* If we're not including merged revisions, and we were given no
2288251881Speter     paths or a single empty (or "/") path, then we can bypass a bunch
2289251881Speter     of complexity because we already know in which revisions the root
2290251881Speter     directory was changed -- all of them.  */
2291251881Speter  if ((! include_merged_revisions)
2292251881Speter      && ((! paths->nelts)
2293251881Speter          || ((paths->nelts == 1)
2294251881Speter              && (svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *))
2295251881Speter                  || (strcmp(APR_ARRAY_IDX(paths, 0, const char *),
2296251881Speter                             "/") == 0)))))
2297251881Speter    {
2298251881Speter      apr_uint64_t send_count = 0;
2299251881Speter      int i;
2300251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
2301251881Speter
2302251881Speter      /* If we are provided an authz callback function, use it to
2303251881Speter         verify that the user has read access to the root path in the
2304251881Speter         first of our revisions.
2305251881Speter
2306251881Speter         ### FIXME:  Strictly speaking, we should be checking this
2307251881Speter         ### access in every revision along the line.  But currently,
2308251881Speter         ### there are no known authz implementations which concern
2309251881Speter         ### themselves with per-revision access.  */
2310251881Speter      if (authz_read_func)
2311251881Speter        {
2312251881Speter          svn_boolean_t readable;
2313251881Speter          svn_fs_root_t *rev_root;
2314251881Speter
2315251881Speter          SVN_ERR(svn_fs_revision_root(&rev_root, fs,
2316251881Speter                                       descending_order ? end : start, pool));
2317251881Speter          SVN_ERR(authz_read_func(&readable, rev_root, "",
2318251881Speter                                  authz_read_baton, pool));
2319251881Speter          if (! readable)
2320251881Speter            return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
2321251881Speter        }
2322251881Speter
2323251881Speter      send_count = end - start + 1;
2324251881Speter      if (limit && send_count > limit)
2325251881Speter        send_count = limit;
2326251881Speter      for (i = 0; i < send_count; ++i)
2327251881Speter        {
2328251881Speter          svn_revnum_t rev;
2329251881Speter
2330251881Speter          svn_pool_clear(iterpool);
2331251881Speter
2332251881Speter          if (descending_order)
2333251881Speter            rev = end - i;
2334251881Speter          else
2335251881Speter            rev = start + i;
2336251881Speter          SVN_ERR(send_log(rev, fs, NULL, NULL, NULL,
2337251881Speter                           discover_changed_paths, FALSE,
2338251881Speter                           FALSE, revprops, FALSE, receiver,
2339251881Speter                           receiver_baton, authz_read_func,
2340251881Speter                           authz_read_baton, iterpool));
2341251881Speter        }
2342251881Speter      svn_pool_destroy(iterpool);
2343251881Speter
2344251881Speter      return SVN_NO_ERROR;
2345251881Speter    }
2346251881Speter
2347251881Speter  /* If we are including merged revisions, then create mergeinfo that
2348251881Speter     represents all of PATHS' history between START and END.  We will use
2349251881Speter     this later to squelch duplicate log revisions that might exist in
2350251881Speter     both natural history and merged-in history.  See
2351251881Speter     http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */
2352251881Speter  if (include_merged_revisions)
2353251881Speter    {
2354251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
2355251881Speter
2356251881Speter      SVN_ERR(get_paths_history_as_mergeinfo(&paths_history_mergeinfo,
2357251881Speter                                             repos, paths, start, end,
2358251881Speter                                             authz_read_func,
2359251881Speter                                             authz_read_baton,
2360251881Speter                                             pool, subpool));
2361251881Speter      svn_pool_destroy(subpool);
2362251881Speter    }
2363251881Speter
2364251881Speter  return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL, start, end,
2365251881Speter                 limit, discover_changed_paths, strict_node_history,
2366251881Speter                 include_merged_revisions, FALSE, FALSE, FALSE, revprops,
2367251881Speter                 descending_order, receiver, receiver_baton,
2368251881Speter                 authz_read_func, authz_read_baton, pool);
2369251881Speter}
2370