1 /*
2  * log.c -- Fetch log data and implement the log queries
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 #include "svn_cmdline.h"
25 #include "svn_client.h"
26 #include "svn_dirent_uri.h"
27 #include "svn_string.h"
28 #include "svn_path.h"
29 #include "svn_error.h"
30 #include "svn_sorts.h"
31 #include "svn_pools.h"
32 #include "svn_hash.h"
33 
34 #include "private/svn_fspath.h"
35 #include "private/svn_subr_private.h"
36 #include "private/svn_sorts_private.h"
37 
38 #include "mergeinfo-normalizer.h"
39 
40 #include "svn_private_config.h"
41 
42 
43 
44 /* Describes all changes of a single revision.
45  * Note that all strings are shared within a given svn_min__log_t instance.
46  */
47 typedef struct log_entry_t
48 {
49   /* Revision being described. */
50   svn_revnum_t revision;
51 
52   /* FS path that is equal or a parent of any in PATHS. */
53   const char *common_base;
54 
55   /* Sorted list of all FS paths touched. Elements are const char*. */
56   apr_array_header_t *paths;
57 } log_entry_t;
58 
59 /* Describes a deletion.
60  * Note that replacements are treated as additions + deletions.
61  */
62 typedef struct deletion_t
63 {
64   /* Path being deleted (or replaced). */
65   const char *path;
66 
67   /* Revision in which this deletion happened.*/
68   svn_revnum_t revision;
69 } deletion_t;
70 
71 /* Note that all FS paths are internalized and shared within this object.
72  */
73 struct svn_min__log_t
74 {
75   /* Dictionary of all FS paths used in this log.
76    * The hash itself is only temporary and will be destroyed after the log
77    * has been constructed and all paths got internalized. */
78   apr_hash_t *unique_paths;
79 
80   /* Oldest revision we received. */
81   svn_revnum_t first_rev;
82 
83   /* Latest revision we received. */
84   svn_revnum_t head_rev;
85 
86   /* Log contents we received.  Entries are log_entry_t *. */
87   apr_array_header_t *entries;
88 
89   /* List of all copy operations we encountered, sorted by target&rev. */
90   apr_array_header_t *copies;
91 
92   /* Like COPIES but sorted by source&source-rev. */
93   apr_array_header_t *copies_by_source;
94 
95   /* List of all deletions we encountered, sorted by path&rev. */
96   apr_array_header_t *deletions;
97 
98   /* If set, don't show progress nor summary. */
99   svn_boolean_t quiet;
100 };
101 
102 /* Comparison function defining the order in svn_min__log_t.COPIES. */
103 static int
copy_order(const void * lhs,const void * rhs)104 copy_order(const void *lhs,
105            const void *rhs)
106 {
107   const svn_min__copy_t *lhs_copy = *(const svn_min__copy_t * const *)lhs;
108   const svn_min__copy_t *rhs_copy = *(const svn_min__copy_t * const *)rhs;
109 
110   int diff = strcmp(lhs_copy->path, rhs_copy->path);
111   if (diff)
112     return diff;
113 
114   if (lhs_copy->revision < rhs_copy->revision)
115     return -1;
116 
117   return lhs_copy->revision == rhs_copy->revision ? 0 : 1;
118 }
119 
120 /* Comparison function defining the order in svn_min__log_t.COPIES_BY_SOURCE.
121  */
122 static int
copy_by_source_order(const void * lhs,const void * rhs)123 copy_by_source_order(const void *lhs,
124                      const void *rhs)
125 {
126   const svn_min__copy_t *lhs_copy = *(const svn_min__copy_t * const *)lhs;
127   const svn_min__copy_t *rhs_copy = *(const svn_min__copy_t * const *)rhs;
128 
129   int diff = strcmp(lhs_copy->copyfrom_path, rhs_copy->copyfrom_path);
130   if (diff)
131     return diff;
132 
133   if (lhs_copy->copyfrom_revision < rhs_copy->copyfrom_revision)
134     return -1;
135 
136   return lhs_copy->copyfrom_revision == rhs_copy->copyfrom_revision ? 0 : 1;
137 }
138 
139 /* Comparison function defining the order in svn_min__log_t.DELETIONS. */
140 static int
deletion_order(const void * lhs,const void * rhs)141 deletion_order(const void *lhs,
142                const void *rhs)
143 {
144   const deletion_t *lhs_deletion = *(const deletion_t * const *)lhs;
145   const deletion_t *rhs_deletion = *(const deletion_t * const *)rhs;
146 
147   int diff = strcmp(lhs_deletion->path, rhs_deletion->path);
148   if (diff)
149     return diff;
150 
151   if (lhs_deletion->revision < rhs_deletion->revision)
152     return -1;
153 
154   return lhs_deletion->revision == rhs_deletion->revision ? 0 : 1;
155 }
156 
157 /* Return the string stored in UNIQUE_PATHS with the value PATH of PATH_LEN
158  * characters.  If the hash does not have a matching entry, add one.
159  * Allocate all strings in RESULT_POOL. */
160 static const char *
internalize(apr_hash_t * unique_paths,const char * path,apr_ssize_t path_len,apr_pool_t * result_pool)161 internalize(apr_hash_t *unique_paths,
162             const char *path,
163             apr_ssize_t path_len,
164             apr_pool_t *result_pool)
165 {
166   const char *result = apr_hash_get(unique_paths, path, path_len);
167   if (result == NULL)
168     {
169       result = apr_pstrmemdup(result_pool, path, path_len);
170       apr_hash_set(unique_paths, result, path_len, result);
171     }
172 
173   return result;
174 }
175 
176 /* Implements svn_log_entry_receiver_t.  Copies the info of LOG_ENTRY into
177  * (svn_min__log_t *)BATON. */
178 static svn_error_t *
log_entry_receiver(void * baton,svn_log_entry_t * log_entry,apr_pool_t * scratch_pool)179 log_entry_receiver(void *baton,
180                    svn_log_entry_t *log_entry,
181                    apr_pool_t *scratch_pool)
182 {
183   svn_min__log_t *log = baton;
184   apr_pool_t *result_pool = log->entries->pool;
185   log_entry_t *entry;
186   apr_hash_index_t *hi;
187   const char *common_base;
188   int count;
189 
190   /* Don't care about empty revisions. Skip them. */
191   if (!log_entry->changed_paths || !apr_hash_count(log_entry->changed_paths))
192     return SVN_NO_ERROR;
193 
194   /* Copy changed paths list. Collect deletions and copies. */
195   entry = apr_pcalloc(result_pool, sizeof(*entry));
196   entry->revision = log_entry->revision;
197   entry->paths = apr_array_make(result_pool,
198                                 apr_hash_count(log_entry->changed_paths),
199                                 sizeof(const char *));
200 
201   for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths);
202        hi;
203        hi = apr_hash_next(hi))
204     {
205       const char *path = apr_hash_this_key(hi);
206       svn_log_changed_path_t *change = apr_hash_this_val(hi);
207 
208       path = internalize(log->unique_paths, path, apr_hash_this_key_len(hi),
209                          result_pool);
210       APR_ARRAY_PUSH(entry->paths, const char *) = path;
211 
212       if (change->action == 'D' || change->action == 'R')
213         {
214           deletion_t *deletion = apr_pcalloc(result_pool, sizeof(*deletion));
215           deletion->path = path;
216           deletion->revision = log_entry->revision;
217 
218           APR_ARRAY_PUSH(log->deletions, deletion_t *) = deletion;
219         }
220 
221       if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
222         {
223           svn_min__copy_t *copy = apr_pcalloc(result_pool, sizeof(*copy));
224           copy->path = path;
225           copy->revision = log_entry->revision;
226           copy->copyfrom_path = internalize(log->unique_paths,
227                                             change->copyfrom_path,
228                                             strlen(change->copyfrom_path),
229                                             result_pool);
230           copy->copyfrom_revision = change->copyfrom_rev;
231 
232           APR_ARRAY_PUSH(log->copies, svn_min__copy_t *) = copy;
233         }
234     }
235 
236   /* Determine the common base of all changed paths. */
237   count = entry->paths->nelts;
238   if (count == 1)
239     {
240       entry->common_base = APR_ARRAY_IDX(entry->paths, 0, const char *);
241     }
242   else
243     {
244       svn_sort__array(entry->paths, svn_sort_compare_paths);
245 
246       common_base = svn_dirent_get_longest_ancestor(
247                       APR_ARRAY_IDX(entry->paths, 0, const char *),
248                       APR_ARRAY_IDX(entry->paths, count - 1, const char *),
249                       scratch_pool);
250       entry->common_base = internalize(log->unique_paths, common_base,
251                                        strlen(common_base), result_pool);
252     }
253 
254   /* Done with that reivison. */
255   APR_ARRAY_PUSH(log->entries, log_entry_t *) = entry;
256 
257   /* Update log-global state. */
258   log->first_rev = log_entry->revision;
259   if (log->head_rev == SVN_INVALID_REVNUM)
260     log->head_rev = log_entry->revision;
261 
262   /* Show progress. */
263   if (log->entries->nelts % 1000 == 0 && !log->quiet)
264     {
265       SVN_ERR(svn_cmdline_printf(scratch_pool, "."));
266       SVN_ERR(svn_cmdline_fflush(stdout));
267     }
268 
269   return SVN_NO_ERROR;
270 }
271 
272 /* Print some statistics about LOG to console. Use SCRATCH_POOL for
273  * temporary allocations. */
274 static svn_error_t *
print_log_stats(svn_min__log_t * log,apr_pool_t * scratch_pool)275 print_log_stats(svn_min__log_t *log,
276                 apr_pool_t *scratch_pool)
277 {
278   int change_count = 0;
279 
280   int i;
281   for (i = 0; i < log->entries->nelts; ++i)
282     {
283       const log_entry_t *entry = APR_ARRAY_IDX(log->entries, i,
284                                                const log_entry_t *);
285       change_count += entry->paths->nelts;
286     }
287 
288   SVN_ERR(svn_cmdline_printf(scratch_pool,
289                              _("    Received %d revisions from %ld to %ld.\n"),
290                              log->entries->nelts, log->first_rev,
291                              log->head_rev));
292   SVN_ERR(svn_cmdline_printf(scratch_pool,
293                              _("    Received %d path changes.\n"),
294                              change_count));
295   SVN_ERR(svn_cmdline_printf(scratch_pool,
296                              _("    Pool has %u different paths.\n\n"),
297                              apr_hash_count(log->unique_paths)));
298 
299   return SVN_NO_ERROR;
300 }
301 
302 svn_error_t *
svn_min__log(svn_min__log_t ** log,const char * url,svn_min__cmd_baton_t * baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)303 svn_min__log(svn_min__log_t **log,
304              const char *url,
305              svn_min__cmd_baton_t *baton,
306              apr_pool_t *result_pool,
307              apr_pool_t *scratch_pool)
308 {
309   svn_client_ctx_t *ctx = baton->ctx;
310   svn_min__log_t *result;
311 
312   /* Prepare API parameters for fetching the full log for URL,
313    * including changed paths, excluding revprops.
314    */
315   apr_array_header_t *targets;
316   apr_array_header_t *revisions;
317   apr_array_header_t *revprops;
318   svn_opt_revision_t peg_revision = { svn_opt_revision_head };
319   svn_opt_revision_range_t range = { { svn_opt_revision_unspecified },
320                                      { svn_opt_revision_unspecified } };
321 
322   targets = apr_array_make(scratch_pool, 1, sizeof(const char *));
323   APR_ARRAY_PUSH(targets, const char *) = url;
324 
325   revisions = apr_array_make(scratch_pool, 1, sizeof(&range));
326   APR_ARRAY_PUSH(revisions, svn_opt_revision_range_t *) = &range;
327 
328   revprops = apr_array_make(scratch_pool, 0, sizeof(const char *));
329 
330   /* The log object to fill. */
331   result = apr_pcalloc(result_pool, sizeof(*result));
332   result->unique_paths = svn_hash__make(scratch_pool);
333   result->first_rev = SVN_INVALID_REVNUM;
334   result->head_rev = SVN_INVALID_REVNUM;
335   result->entries = apr_array_make(result_pool, 1024, sizeof(log_entry_t *));
336   result->copies = apr_array_make(result_pool, 1024,
337                                   sizeof(svn_min__copy_t *));
338   result->deletions = apr_array_make(result_pool, 1024, sizeof(deletion_t *));
339   result->quiet = baton->opt_state->quiet;
340 
341   if (!baton->opt_state->quiet)
342     {
343       SVN_ERR(svn_cmdline_printf(scratch_pool,
344                                  _("Fetching log for %s ..."),
345                                  url));
346       SVN_ERR(svn_cmdline_fflush(stdout));
347     }
348 
349   SVN_ERR(svn_client_log5(targets,
350                           &peg_revision,
351                           revisions,
352                           0, /* no limit */
353                           TRUE, /* verbose */
354                           TRUE, /* stop-on-copy */
355                           FALSE, /* merge history */
356                           revprops,
357                           log_entry_receiver,
358                           result,
359                           ctx,
360                           scratch_pool));
361 
362   /* Complete arrays in RESULT. */
363   result->copies_by_source = apr_array_copy(result_pool, result->copies);
364 
365   svn_sort__array_reverse(result->entries, scratch_pool);
366   svn_sort__array(result->copies, copy_order);
367   svn_sort__array(result->copies_by_source, copy_by_source_order);
368   svn_sort__array(result->deletions, deletion_order);
369 
370   /* Show that we are done. */
371   if (!baton->opt_state->quiet)
372     {
373       SVN_ERR(svn_cmdline_printf(scratch_pool, "\n"));
374       SVN_ERR(print_log_stats(result, scratch_pool));
375     }
376 
377   result->unique_paths = NULL;
378   *log = result;
379 
380   return SVN_NO_ERROR;
381 }
382 
383 /* Append REVISION with the INHERITABLE setting to RANGES.  RANGES must be
384  * sorted and REVISION must be larger than the largest revision in RANGES. */
385 static void
append_rev_to_ranges(svn_rangelist_t * ranges,svn_revnum_t revision,svn_boolean_t inheritable)386 append_rev_to_ranges(svn_rangelist_t *ranges,
387                      svn_revnum_t revision,
388                      svn_boolean_t inheritable)
389 {
390   /* In many cases, we can save memory by simply extending the last range. */
391   svn_merge_range_t *range;
392   if (ranges->nelts)
393     {
394       range = APR_ARRAY_IDX(ranges, ranges->nelts - 1, svn_merge_range_t *);
395       if (range->end + 1 == revision && range->inheritable == inheritable)
396         {
397           range->end = revision;
398           return;
399         }
400     }
401 
402   /* We need to add a new range. */
403   range = apr_pcalloc(ranges->pool, sizeof(*range));
404   range->start = revision - 1;
405   range->end = revision;
406   range->inheritable = inheritable;
407 
408   APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range;
409 }
410 
411 /* Comparison function comparing the log_entry_t * in *LHS with the
412  * svn_revnum_t in *rhs.
413  */
414 static int
compare_rev_log_entry(const void * lhs,const void * rhs)415 compare_rev_log_entry(const void *lhs,
416                       const void *rhs)
417 {
418   const log_entry_t *entry = *(const log_entry_t * const *)lhs;
419   svn_revnum_t revision = *(const svn_revnum_t *)rhs;
420 
421   if (entry->revision < revision)
422     return -1;
423 
424   return entry->revision == revision ? 0 : 1;
425 }
426 
427 /* Restrict RANGE to the range of revisions covered by LOG. Cut-off from
428  * both sides will be added to RANGES. */
429 static void
restrict_range(svn_min__log_t * log,svn_merge_range_t * range,svn_rangelist_t * ranges)430 restrict_range(svn_min__log_t *log,
431                svn_merge_range_t *range,
432                svn_rangelist_t *ranges)
433 {
434   /* Cut off at the earliest revision. */
435   if (range->start + 1 < log->first_rev)
436     {
437       svn_merge_range_t *new_range
438         = apr_pmemdup(ranges->pool, range, sizeof(*range));
439       new_range->end = MIN(new_range->end, log->first_rev - 1);
440 
441       APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = new_range;
442       range->start = new_range->end;
443     }
444 
445   /* Cut off at log HEAD. */
446   if (range->end > log->head_rev)
447     {
448       svn_merge_range_t *new_range
449         = apr_pmemdup(ranges->pool, range, sizeof(*range));
450       new_range->start = log->head_rev;
451 
452       APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = new_range;
453       range->end = new_range->start;
454     }
455 }
456 
457 /* Return TRUE if PATH is either equal to, a parent of or sub-path of
458  * CHANGED_PATH. */
459 static svn_boolean_t
is_relevant(const char * changed_path,const char * path)460 is_relevant(const char *changed_path,
461             const char *path)
462 {
463   return  svn_dirent_is_ancestor(changed_path, path)
464        || svn_dirent_is_ancestor(path, changed_path);
465 }
466 
467 /* Return TRUE if PATH is either equal to, a parent of or sub-path of
468  * SUB_TREE.  Ignore REVISION and BATON but keep it for a unified signature
469  * to be used with filter_ranges. */
470 static svn_boolean_t
in_subtree(const char * changed_path,const char * sub_tree,svn_revnum_t revision,const void * baton)471 in_subtree(const char *changed_path,
472            const char *sub_tree,
473            svn_revnum_t revision,
474            const void *baton)
475 {
476   return svn_dirent_is_ancestor(sub_tree, changed_path);
477 }
478 
479 /* Return TRUE if
480  * - CHANGED_PATH is is either equal to or a sub-node of PATH, and
481  * - CHNAGED_PATH is outside the sub-tree given as BATON.
482  * Ignore REVISION. */
483 static svn_boolean_t
below_path_outside_subtree(const char * changed_path,const char * path,svn_revnum_t revision,const void * baton)484 below_path_outside_subtree(const char *changed_path,
485                            const char *path,
486                            svn_revnum_t revision,
487                            const void *baton)
488 {
489   const char *subtree = baton;
490 
491   /* Is this a change _below_ PATH but not within SUBTREE? */
492   return   !svn_dirent_is_ancestor(subtree, changed_path)
493         && svn_dirent_is_ancestor(path, changed_path)
494         && strcmp(path, changed_path);
495 }
496 
497 /* Baton struct to be used with change_outside_all_subtree_ranges. */
498 typedef struct change_outside_baton_t
499 {
500   /* Maps FS path to revision range lists. */
501   apr_hash_t *sibling_ranges;
502 
503   /* Pool for temporary allocations.
504    * Baton users may clear this at will. */
505   apr_pool_t *iterpool;
506 } change_outside_baton_t;
507 
508 /* Comparison function comparing range *LHS to revision *RHS. */
509 static int
compare_range_rev(const void * lhs,const void * rhs)510 compare_range_rev(const void *lhs,
511                   const void *rhs)
512 {
513   const svn_merge_range_t *range = *(const svn_merge_range_t * const *)lhs;
514   svn_revnum_t revision = *(const svn_revnum_t *)rhs;
515 
516   if (range->start >= revision)
517     return 1;
518 
519   return range->end < revision ? 1 : 0;
520 }
521 
522 /* Return TRUE if CHANGED_PATH is either equal to or a sub-node of PATH,
523  * CHNAGED_PATH@REVISION is not covered by BATON->SIBLING_RANGES. */
524 static svn_boolean_t
change_outside_all_subtree_ranges(const char * changed_path,const char * path,svn_revnum_t revision,const void * baton)525 change_outside_all_subtree_ranges(const char *changed_path,
526                                   const char *path,
527                                   svn_revnum_t revision,
528                                   const void *baton)
529 {
530   const change_outside_baton_t *b = baton;
531   svn_boolean_t missing = TRUE;
532   apr_size_t len;
533   svn_rangelist_t *ranges;
534 
535   /* Don't collect changes outside the subtree starting at PARENT_PATH. */
536   if (!svn_dirent_is_ancestor(path, changed_path))
537     return FALSE;
538 
539   svn_pool_clear(b->iterpool);
540 
541   /* All branches that contain CHANGED_PATH, i.e. match either it or one
542    * of its parents, must mention REVISION in their mergeinfo. */
543   for (len = strlen(changed_path);
544        !svn_fspath__is_root(changed_path, len);
545        len = strlen(changed_path))
546     {
547       ranges = apr_hash_get(b->sibling_ranges, changed_path, len);
548       if (ranges)
549         {
550           /* If any of the matching branches does not list REVISION
551            * as already merged, we found an "outside" change. */
552           if (!svn_sort__array_lookup(ranges, &revision, NULL,
553                                       compare_range_rev))
554             return TRUE;
555 
556           /* Mergeinfo for this path has been found. */
557           missing = FALSE;
558         }
559 
560       changed_path = svn_fspath__dirname(changed_path, b->iterpool);
561     }
562 
563   /* Record, if no mergeinfo has been found for this CHANGED_PATH. */
564   return missing;
565 }
566 
567 /* In LOG, scan the revisions given in RANGES and return the revision /
568  * ranges that are relevant to PATH with respect to the CHANGE_RELEVANT
569  * criterion using BATON.  Keep revisions that lie outside what is covered
570  * by LOG. Allocate the result in RESULT_POOL. */
571 static svn_rangelist_t *
filter_ranges(svn_min__log_t * log,const char * path,svn_rangelist_t * ranges,svn_boolean_t (* change_relavent)(const char *,const char *,svn_revnum_t,const void *),const void * baton,apr_pool_t * result_pool)572 filter_ranges(svn_min__log_t *log,
573               const char *path,
574               svn_rangelist_t *ranges,
575               svn_boolean_t (*change_relavent)(const char *,
576                                                const char *,
577                                                svn_revnum_t,
578                                                const void *),
579               const void *baton,
580               apr_pool_t *result_pool)
581 {
582   svn_rangelist_t *result;
583   int i, k, l;
584 
585   /* Auto-complete parameters. */
586   if (!SVN_IS_VALID_REVNUM(log->first_rev))
587     return svn_rangelist_dup(ranges, result_pool);
588 
589   result = apr_array_make(result_pool, 0, ranges->elt_size);
590   for (i = 0; i < ranges->nelts; ++i)
591     {
592       /* Next revision range to scan. */
593       svn_merge_range_t range
594         = *APR_ARRAY_IDX(ranges, i, const svn_merge_range_t *);
595       restrict_range(log, &range, result);
596 
597       /* Find the range start and scan the range linearly. */
598       ++range.start;
599       for (k = svn_sort__bsearch_lower_bound(log->entries, &range.start,
600                                              compare_rev_log_entry);
601            k < log->entries->nelts;
602            ++k)
603         {
604           const log_entry_t *entry = APR_ARRAY_IDX(log->entries, k,
605                                                   const log_entry_t *);
606           if (entry->revision > range.end)
607             break;
608 
609           /* Skip revisions no relevant to PATH. */
610           if (!is_relevant(entry->common_base, path))
611             continue;
612 
613           /* Look for any changed path that meets the filter criterion. */
614           for (l = 0; l < entry->paths->nelts; ++l)
615             {
616               const char *changed_path
617                 = APR_ARRAY_IDX(entry->paths, l, const char *);
618 
619               if (change_relavent(changed_path, path, entry->revision, baton))
620                 {
621                   append_rev_to_ranges(result, entry->revision,
622                                        range.inheritable);
623                   break;
624                 }
625             }
626         }
627     }
628 
629   return result;
630 }
631 
632 svn_rangelist_t *
svn_min__operative(svn_min__log_t * log,const char * path,svn_rangelist_t * ranges,apr_pool_t * result_pool)633 svn_min__operative(svn_min__log_t *log,
634                    const char *path,
635                    svn_rangelist_t *ranges,
636                    apr_pool_t *result_pool)
637 {
638   return filter_ranges(log, path, ranges, in_subtree, NULL, result_pool);
639 }
640 
641 svn_rangelist_t *
svn_min__operative_outside_subtree(svn_min__log_t * log,const char * path,const char * subtree,svn_rangelist_t * ranges,apr_pool_t * result_pool)642 svn_min__operative_outside_subtree(svn_min__log_t *log,
643                                    const char *path,
644                                    const char *subtree,
645                                    svn_rangelist_t *ranges,
646                                    apr_pool_t *result_pool)
647 {
648   return filter_ranges(log, path, ranges, below_path_outside_subtree,
649                        subtree, result_pool);
650 }
651 
652 svn_rangelist_t *
svn_min__operative_outside_all_subtrees(svn_min__log_t * log,const char * path,svn_rangelist_t * ranges,apr_hash_t * sibling_ranges,apr_pool_t * result_pool,apr_pool_t * scratch_pool)653 svn_min__operative_outside_all_subtrees(svn_min__log_t *log,
654                                         const char *path,
655                                         svn_rangelist_t *ranges,
656                                         apr_hash_t *sibling_ranges,
657                                         apr_pool_t *result_pool,
658                                         apr_pool_t *scratch_pool)
659 {
660   svn_rangelist_t *result;
661   change_outside_baton_t baton;
662   baton.sibling_ranges = sibling_ranges;
663   baton.iterpool = svn_pool_create(scratch_pool);
664 
665   result = filter_ranges(log, path, ranges, change_outside_all_subtree_ranges,
666                          &baton, result_pool);
667   svn_pool_destroy(baton.iterpool);
668 
669   return result;
670 }
671 
672 svn_revnum_t
svn_min__find_deletion(svn_min__log_t * log,const char * path,svn_revnum_t start_rev,svn_revnum_t end_rev,apr_pool_t * scratch_pool)673 svn_min__find_deletion(svn_min__log_t *log,
674                        const char *path,
675                        svn_revnum_t start_rev,
676                        svn_revnum_t end_rev,
677                        apr_pool_t *scratch_pool)
678 {
679   svn_revnum_t latest = SVN_INVALID_REVNUM;
680 
681   deletion_t *to_find = apr_pcalloc(scratch_pool, sizeof(*to_find));
682   to_find->path = path;
683   to_find->revision = end_rev;
684 
685   /* Auto-complete parameters. */
686   if (!SVN_IS_VALID_REVNUM(start_rev))
687     start_rev = log->head_rev;
688 
689   /* Walk up the tree and find the latest deletion of PATH or any of
690    * its parents. */
691   while (!svn_fspath__is_root(to_find->path, strlen(to_find->path)))
692     {
693       int i;
694       for (i = svn_sort__bsearch_lower_bound(log->deletions, &to_find,
695                                              deletion_order);
696            i < log->deletions->nelts;
697            ++i)
698         {
699           const deletion_t *deletion = APR_ARRAY_IDX(log->deletions, i,
700                                                      const deletion_t *);
701           if (strcmp(deletion->path, to_find->path))
702             break;
703           if (deletion->revision > start_rev)
704             break;
705 
706           latest = deletion->revision;
707           to_find->revision = deletion->revision;
708         }
709 
710       to_find->path = svn_fspath__dirname(to_find->path, scratch_pool);
711     }
712 
713   return latest;
714 }
715 
716 apr_array_header_t *
svn_min__find_deletions(svn_min__log_t * log,const char * path,apr_pool_t * result_pool,apr_pool_t * scratch_pool)717 svn_min__find_deletions(svn_min__log_t *log,
718                         const char *path,
719                         apr_pool_t *result_pool,
720                         apr_pool_t *scratch_pool)
721 {
722   apr_array_header_t *result = apr_array_make(result_pool, 0,
723                                               sizeof(svn_revnum_t));
724   int source, dest;
725 
726   deletion_t *to_find = apr_pcalloc(scratch_pool, sizeof(*to_find));
727   to_find->path = path;
728   to_find->revision = 0;
729 
730   /* Find deletions for PATH and its parents. */
731   if (!svn_fspath__is_root(to_find->path, strlen(to_find->path)))
732     {
733       int i;
734       for (i = svn_sort__bsearch_lower_bound(log->deletions, &to_find,
735                                              deletion_order);
736            i < log->deletions->nelts;
737            ++i)
738         {
739           const deletion_t *deletion = APR_ARRAY_IDX(log->deletions, i,
740                                                      const deletion_t *);
741           if (strcmp(deletion->path, to_find->path))
742             break;
743 
744           APR_ARRAY_PUSH(result, svn_revnum_t) = deletion->revision;
745         }
746 
747       to_find->path = svn_fspath__dirname(to_find->path, scratch_pool);
748     }
749 
750   /* Remove any duplicates (unlikely but possible). */
751   svn_sort__array(result, svn_sort_compare_revisions);
752   for (source = 1, dest = 0; source < result->nelts; ++source)
753     {
754       svn_revnum_t source_rev = APR_ARRAY_IDX(result, source, svn_revnum_t);
755       svn_revnum_t dest_rev = APR_ARRAY_IDX(result, dest, svn_revnum_t);
756       if (source_rev != dest_rev)
757         {
758           ++dest_rev;
759           APR_ARRAY_IDX(result, dest, svn_revnum_t) = source_rev;
760         }
761     }
762 
763   if (result->nelts)
764     result->nelts = dest + 1;
765 
766   return result;
767 }
768 
769 /* Starting at REVISION, scan LOG for the next (in REVISION or older) copy
770  * that creates PATH explicitly or implicitly by creating a parent of it.
771  * Return the copy operation found or NULL if none exists.  Use SCRATCH_POOL
772  * for temporary allocations. */
773 static const svn_min__copy_t *
next_copy(svn_min__log_t * log,const char * path,svn_revnum_t revision,apr_pool_t * scratch_pool)774 next_copy(svn_min__log_t *log,
775           const char *path,
776           svn_revnum_t revision,
777           apr_pool_t *scratch_pool)
778 {
779   const svn_min__copy_t *copy = NULL;
780   int idx;
781 
782   svn_min__copy_t *to_find = apr_pcalloc(scratch_pool, sizeof(*to_find));
783   to_find->path = path;
784   to_find->revision = revision;
785 
786   idx = svn_sort__bsearch_lower_bound(log->copies, &to_find, copy_order);
787   if (idx < log->copies->nelts)
788     {
789       /* Found an exact match? */
790       copy = APR_ARRAY_IDX(log->copies, idx, const svn_min__copy_t *);
791       if (copy->revision != revision || strcmp(copy->path, path))
792         copy = NULL;
793     }
794 
795   if (!copy && idx > 0)
796     {
797       /* No exact match. The predecessor may be the closest copy. */
798       copy = APR_ARRAY_IDX(log->copies, idx - 1, const svn_min__copy_t *);
799       if (strcmp(copy->path, path))
800         copy = NULL;
801     }
802 
803   /* Mabye, the parent folder got copied later, i.e. is the closest copy.
804      We implicitly recurse up the tree. */
805   if (!svn_fspath__is_root(to_find->path, strlen(to_find->path)))
806     {
807       const svn_min__copy_t *parent_copy;
808       to_find->path = svn_fspath__dirname(to_find->path, scratch_pool);
809 
810       parent_copy = next_copy(log, to_find->path, revision, scratch_pool);
811       if (!copy)
812         copy = parent_copy;
813       else if (parent_copy && parent_copy->revision > copy->revision)
814         copy = parent_copy;
815     }
816 
817   return copy;
818 }
819 
820 svn_revnum_t
svn_min__find_copy(svn_min__log_t * log,const char * path,svn_revnum_t start_rev,svn_revnum_t end_rev,apr_pool_t * scratch_pool)821 svn_min__find_copy(svn_min__log_t *log,
822                    const char *path,
823                    svn_revnum_t start_rev,
824                    svn_revnum_t end_rev,
825                    apr_pool_t *scratch_pool)
826 {
827   const svn_min__copy_t *copy;
828 
829   /* Auto-complete parameters. */
830   if (!SVN_IS_VALID_REVNUM(start_rev))
831     start_rev = log->head_rev;
832 
833   /* The actual lookup. */
834   copy = next_copy(log, path, start_rev, scratch_pool);
835   if (copy && copy->revision >= end_rev)
836     return copy->revision;
837 
838   return SVN_NO_ERROR;
839 }
840 
841 apr_array_header_t *
svn_min__get_copies(svn_min__log_t * log,const char * path,svn_revnum_t start_rev,svn_revnum_t end_rev,apr_pool_t * result_pool,apr_pool_t * scratch_pool)842 svn_min__get_copies(svn_min__log_t *log,
843                     const char *path,
844                     svn_revnum_t start_rev,
845                     svn_revnum_t end_rev,
846                     apr_pool_t *result_pool,
847                     apr_pool_t *scratch_pool)
848 {
849   apr_array_header_t *result = apr_array_make(result_pool, 0,
850                                               sizeof(svn_min__copy_t *));
851   const svn_min__copy_t **copies = (void *)log->copies_by_source->elts;
852   int idx;
853 
854   /* Find all sub-tree copies, including PATH. */
855   svn_min__copy_t *to_find = apr_pcalloc(scratch_pool, sizeof(*to_find));
856   to_find->copyfrom_path = path;
857   to_find->copyfrom_revision = end_rev;
858 
859   for (idx = svn_sort__bsearch_lower_bound(log->copies_by_source,
860                                            &to_find,
861                                            copy_by_source_order);
862           (idx < log->copies->nelts)
863        && svn_dirent_is_ancestor(path, copies[idx]->copyfrom_path);
864        ++idx)
865     {
866       if (copies[idx]->copyfrom_revision <= start_rev)
867         APR_ARRAY_PUSH(result, const svn_min__copy_t *) = copies[idx];
868     }
869 
870   /* Find all parent copies. */
871   while (!svn_fspath__is_root(to_find->copyfrom_path,
872                               strlen(to_find->copyfrom_path)))
873     {
874       to_find->copyfrom_path = svn_fspath__dirname(to_find->copyfrom_path,
875                                                    scratch_pool);
876 
877       for (idx = svn_sort__bsearch_lower_bound(log->copies_by_source,
878                                                &to_find,
879                                                copy_by_source_order);
880               (idx < log->copies->nelts)
881            && !strcmp(copies[idx]->copyfrom_path, to_find->copyfrom_path)
882            && (copies[idx]->copyfrom_revision <= start_rev);
883            ++idx)
884         {
885           APR_ARRAY_PUSH(result, const svn_min__copy_t *) = copies[idx];
886         }
887     }
888 
889   return result;
890 }
891 
892 /* A history segment.  Simply a FS path plus the revision range that it is
893  * part of the history of the node. */
894 typedef struct segment_t
895 {
896   /* FS path at which the node lives in this segment */
897   const char *path;
898 
899   /* Revision that it appears in or that the history was truncated to. */
900   svn_revnum_t start;
901 
902   /* Revision from which the node was copied to the next segment or the
903    * revision that the history was truncated to. */
904   svn_revnum_t end;
905 } segment_t;
906 
907 apr_array_header_t *
svn_min__get_history(svn_min__log_t * log,const char * path,svn_revnum_t start_rev,svn_revnum_t end_rev,apr_pool_t * result_pool,apr_pool_t * scratch_pool)908 svn_min__get_history(svn_min__log_t *log,
909                      const char *path,
910                      svn_revnum_t start_rev,
911                      svn_revnum_t end_rev,
912                      apr_pool_t *result_pool,
913                      apr_pool_t *scratch_pool)
914 {
915   segment_t *segment;
916   const svn_min__copy_t *copy;
917   apr_array_header_t *result = apr_array_make(result_pool, 16,
918                                               sizeof(segment_t *));
919 
920   /* Auto-complete parameters. */
921   if (!SVN_IS_VALID_REVNUM(start_rev))
922     start_rev = log->head_rev;
923 
924   /* Simply follow all copies, each time adding a segment from "here" to
925    * the next copy. */
926   for (copy = next_copy(log, path, start_rev, scratch_pool);
927        copy && start_rev >= end_rev;
928        copy = next_copy(log, path, start_rev, scratch_pool))
929     {
930       segment = apr_pcalloc(result_pool, sizeof(*segment));
931       segment->start = MAX(end_rev, copy->revision);
932       segment->end = start_rev;
933       segment->path = apr_pstrdup(result_pool, path);
934 
935       APR_ARRAY_PUSH(result, segment_t *) = segment;
936 
937       start_rev = copy->copyfrom_revision;
938       path = svn_fspath__join(copy->copyfrom_path,
939                               svn_fspath__skip_ancestor(copy->path, path),
940                               scratch_pool);
941     }
942 
943   /* The final segment has no copy-from. */
944   if (start_rev >= end_rev)
945     {
946       segment = apr_pcalloc(result_pool, sizeof(*segment));
947       segment->start = end_rev;
948       segment->end = start_rev;
949       segment->path = apr_pstrdup(result_pool, path);
950 
951       APR_ARRAY_PUSH(result, segment_t *) = segment;
952     }
953 
954   return result;
955 }
956 
957 apr_array_header_t *
svn_min__intersect_history(apr_array_header_t * lhs,apr_array_header_t * rhs,apr_pool_t * result_pool)958 svn_min__intersect_history(apr_array_header_t *lhs,
959                            apr_array_header_t *rhs,
960                            apr_pool_t *result_pool)
961 {
962   apr_array_header_t *result = apr_array_make(result_pool, 16,
963                                               sizeof(segment_t *));
964 
965   int lhs_idx = 0;
966   int rhs_idx = 0;
967 
968   /* Careful: the segments are ordered latest to oldest. */
969   while (lhs_idx < lhs->nelts && rhs_idx < rhs->nelts)
970     {
971       segment_t *lhs_segment = APR_ARRAY_IDX(lhs, lhs_idx, segment_t *);
972       segment_t *rhs_segment = APR_ARRAY_IDX(rhs, rhs_idx, segment_t *);
973 
974       /* Skip non-overlapping revision segments */
975       if (lhs_segment->start > rhs_segment->end)
976         {
977           ++lhs_idx;
978           continue;
979         }
980       else if (lhs_segment->end < rhs_segment->start)
981         {
982           ++rhs_idx;
983           continue;
984         }
985 
986       /* Revision ranges overlap. Also the same path? */
987       if (!strcmp(lhs_segment->path, rhs_segment->path))
988         {
989           segment_t *segment = apr_pcalloc(result_pool, sizeof(*segment));
990           segment->start = MAX(lhs_segment->start, rhs_segment->start);
991           segment->end = MIN(lhs_segment->end, rhs_segment->end);
992           segment->path = apr_pstrdup(result_pool, lhs_segment->path);
993 
994           APR_ARRAY_PUSH(result, segment_t *) = segment;
995         }
996 
997       /* The segment that starts earlier may overlap with another one.
998          If they should start at the same rev, the next iteration will
999          skip the respective other segment. */
1000       if (lhs_segment->start > rhs_segment->start)
1001         ++lhs_idx;
1002       else
1003         ++rhs_idx;
1004     }
1005 
1006    return result;
1007 }
1008 
1009 svn_rangelist_t *
svn_min__history_ranges(apr_array_header_t * history,apr_pool_t * result_pool)1010 svn_min__history_ranges(apr_array_header_t *history,
1011                         apr_pool_t *result_pool)
1012 {
1013   svn_rangelist_t *result = apr_array_make(result_pool, history->nelts,
1014                                            sizeof(svn_merge_range_t *));
1015 
1016   int i;
1017   for (i = 0; i < history->nelts; ++i)
1018     {
1019       const segment_t *segment = APR_ARRAY_IDX(history, i, segment_t *);
1020 
1021       /* Convert to merge ranges.  Note that start+1 is the first rev
1022          actually in that range. */
1023       svn_merge_range_t *range = apr_pcalloc(result_pool, sizeof(*range));
1024       range->start = MAX(0, segment->start - 1);
1025       range->end = segment->end;
1026       range->inheritable = TRUE;
1027 
1028       APR_ARRAY_PUSH(result, svn_merge_range_t *) = range;
1029     }
1030 
1031   return result;
1032 }
1033