1 /*
2  * wc_mergeinfo.c -- Query and store the mergeinfo.
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 /* ==================================================================== */
25 
26 
27 
28 /*** Includes. ***/
29 
30 #include "svn_cmdline.h"
31 #include "svn_pools.h"
32 #include "svn_client.h"
33 #include "svn_string.h"
34 #include "svn_sorts.h"
35 #include "svn_dirent_uri.h"
36 #include "svn_props.h"
37 #include "svn_hash.h"
38 
39 #include "mergeinfo-normalizer.h"
40 
41 #include "private/svn_fspath.h"
42 #include "private/svn_opt_private.h"
43 #include "private/svn_sorts_private.h"
44 #include "private/svn_subr_private.h"
45 #include "svn_private_config.h"
46 
47 
48 
49 /* Our internal mergeinfo structure
50  * It decorates the standard svn_mergeinfo_t with path and parent info. */
51 typedef struct mergeinfo_t
52 {
53   /* The abspath of the working copy node that has this MERGINFO. */
54   const char *local_path;
55 
56   /* The corresponding FS path. */
57   const char *fs_path;
58 
59   /* The full URL of that node in the repository. */
60   const char *url;
61 
62   /* Pointer to the closest parent mergeinfo that we found in the working
63    * copy.  May be NULL. */
64   struct mergeinfo_t *parent;
65 
66   /* All mergeinfo_t* who's PARENT points to this.  May be NULL. */
67   apr_array_header_t *children;
68 
69   /* The parsed mergeinfo. */
70   svn_mergeinfo_t mergeinfo;
71 } mergeinfo_t;
72 
73 /* Parse the mergeinfo in PROPS as returned by svn_client_propget5,
74  * construct our internal mergeinfo representation, allocated in
75  * RESULT_POOL from it and return it *RESULT_P.  Use SCRATCH_POOL for
76  * temporary allocations. */
77 static svn_error_t *
parse_mergeinfo(apr_array_header_t ** result_p,apr_hash_t * props,apr_pool_t * result_pool,apr_pool_t * scratch_pool)78 parse_mergeinfo(apr_array_header_t **result_p,
79                 apr_hash_t *props,
80                 apr_pool_t *result_pool,
81                 apr_pool_t *scratch_pool)
82 {
83   apr_array_header_t *result = apr_array_make(result_pool,
84                                               apr_hash_count(props),
85                                               sizeof(mergeinfo_t *));
86   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
87   apr_hash_index_t *hi;
88 
89   for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
90     {
91       mergeinfo_t *entry = apr_pcalloc(result_pool, sizeof(*entry));
92       svn_mergeinfo_t mergeinfo;
93       svn_string_t *mi_string = apr_hash_this_val(hi);
94 
95       svn_pool_clear(iterpool);
96       SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mi_string->data, iterpool));
97 
98       entry->local_path = apr_pstrdup(result_pool, apr_hash_this_key(hi));
99       entry->mergeinfo = svn_mergeinfo_dup(mergeinfo, result_pool);
100 
101       APR_ARRAY_PUSH(result, mergeinfo_t *) = entry;
102     }
103 
104   svn_pool_destroy(iterpool);
105   *result_p = result;
106 
107   return SVN_NO_ERROR;
108 }
109 
110 /* Ordering function comparing two mergeinfo_t * by local abspath. */
111 static int
compare_mergeinfo(const void * lhs,const void * rhs)112 compare_mergeinfo(const void *lhs,
113                   const void *rhs)
114 {
115   const mergeinfo_t *lhs_mi = *(const mergeinfo_t *const *)lhs;
116   const mergeinfo_t *rhs_mi = *(const mergeinfo_t *const *)rhs;
117 
118   return strcmp(lhs_mi->local_path, rhs_mi->local_path);
119 }
120 
121 /* Implements svn_client_info_receiver2_t.
122  * Updates the mergeinfo_t * given as BATON with the incoming INFO. */
123 static svn_error_t *
get_urls(void * baton,const char * target,const svn_client_info2_t * info,apr_pool_t * pool)124 get_urls(void *baton,
125          const char *target,
126          const svn_client_info2_t *info,
127          apr_pool_t *pool)
128 {
129   mergeinfo_t *mi = baton;
130   apr_pool_t *target_pool = apr_hash_pool_get(mi->mergeinfo);
131   const char *rel_path = svn_uri_skip_ancestor(info->repos_root_URL,
132                                                info->URL, pool);
133 
134   mi->url = apr_pstrdup(target_pool, info->URL);
135   mi->fs_path = svn_fspath__canonicalize(rel_path, target_pool);
136 
137   return SVN_NO_ERROR;
138 }
139 
140 /* Sort the nodes in MERGEINFO, sub-nodes first, add working copy info to
141  * it and link nodes to their respective closest parents.  BATON provides
142  * the client context.  SCRATCH_POOL is used for temporaries. */
143 static svn_error_t *
link_parents(apr_array_header_t * mergeinfo,svn_min__cmd_baton_t * baton,apr_pool_t * scratch_pool)144 link_parents(apr_array_header_t *mergeinfo,
145              svn_min__cmd_baton_t *baton,
146              apr_pool_t *scratch_pool)
147 {
148   apr_pool_t *result_pool = mergeinfo->pool;
149   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
150   int i;
151 
152   /* We further down assume that there are is least one entry. */
153   if (mergeinfo->nelts == 0)
154     return SVN_NO_ERROR;
155 
156   /* sort mergeinfo by path */
157   svn_sort__array(mergeinfo, compare_mergeinfo);
158 
159   /* add URL info */
160   for (i = 0; i < mergeinfo->nelts; ++i)
161     {
162       mergeinfo_t *entry = APR_ARRAY_IDX(mergeinfo, i, mergeinfo_t *);
163       const svn_opt_revision_t rev_working = { svn_opt_revision_working };
164 
165       svn_pool_clear(iterpool);
166       SVN_ERR(svn_client_info4(entry->local_path, &rev_working,
167                                &rev_working, svn_depth_empty, FALSE,
168                                TRUE, FALSE, NULL, get_urls, entry,
169                                baton->ctx, iterpool));
170     }
171 
172   /* link all mergeinfo to their parent merge info - if that exists */
173   for (i = 1; i < mergeinfo->nelts; ++i)
174     {
175       mergeinfo_t *entry = APR_ARRAY_IDX(mergeinfo, i, mergeinfo_t *);
176       entry->parent = APR_ARRAY_IDX(mergeinfo, i - 1, mergeinfo_t *);
177 
178       while (   entry->parent
179              && !svn_dirent_is_ancestor(entry->parent->local_path,
180                                         entry->local_path))
181         entry->parent = entry->parent->parent;
182 
183       /* Reverse pointer. */
184       if (entry->parent)
185         {
186           if (!entry->parent->children)
187             entry->parent->children
188               = apr_array_make(result_pool, 4, sizeof(svn_mergeinfo_t));
189 
190           APR_ARRAY_PUSH(entry->parent->children, svn_mergeinfo_t)
191             = entry->mergeinfo;
192         }
193     }
194 
195   /* break links for switched paths */
196   for (i = 1; i < mergeinfo->nelts; ++i)
197     {
198       mergeinfo_t *entry = APR_ARRAY_IDX(mergeinfo, i, mergeinfo_t *);
199       if (entry->parent)
200         {
201           if (!svn_uri__is_ancestor(entry->parent->url, entry->url))
202             entry->parent = NULL;
203         }
204     }
205 
206   svn_pool_destroy(iterpool);
207 
208   return SVN_NO_ERROR;
209 }
210 
211 svn_error_t *
svn_min__read_mergeinfo(apr_array_header_t ** result,svn_min__cmd_baton_t * baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)212 svn_min__read_mergeinfo(apr_array_header_t **result,
213                         svn_min__cmd_baton_t *baton,
214                         apr_pool_t *result_pool,
215                         apr_pool_t *scratch_pool)
216 {
217   svn_min__opt_state_t *opt_state = baton->opt_state;
218   svn_client_ctx_t *ctx = baton->ctx;
219 
220   /* Pools for temporary data - to be cleaned up asap as they
221    * significant amounts of it. */
222   apr_pool_t *props_pool = svn_pool_create(scratch_pool);
223   apr_pool_t *props_scratch_pool = svn_pool_create(scratch_pool);
224   apr_hash_t *props;
225 
226   const svn_opt_revision_t rev_working = { svn_opt_revision_working };
227 
228   if (!baton->opt_state->quiet)
229     SVN_ERR(svn_cmdline_printf(scratch_pool,
230                               _("Scanning working copy %s ...\n"),
231                               baton->local_abspath));
232 
233   SVN_ERR(svn_client_propget5(&props, NULL, SVN_PROP_MERGEINFO,
234                               baton->local_abspath, &rev_working,
235                               &rev_working, NULL,
236                               opt_state->depth, NULL, ctx,
237                               props_pool, props_scratch_pool));
238   svn_pool_destroy(props_scratch_pool);
239 
240   SVN_ERR(parse_mergeinfo(result, props, result_pool, scratch_pool));
241   svn_pool_destroy(props_pool);
242 
243   SVN_ERR(link_parents(*result, baton, scratch_pool));
244 
245   if (!baton->opt_state->quiet)
246     SVN_ERR(svn_min__print_mergeinfo_stats(*result, scratch_pool));
247 
248   return SVN_NO_ERROR;
249 }
250 
251 const char *
svn_min__common_parent(apr_array_header_t * mergeinfo,apr_pool_t * result_pool,apr_pool_t * scratch_pool)252 svn_min__common_parent(apr_array_header_t *mergeinfo,
253                        apr_pool_t *result_pool,
254                        apr_pool_t *scratch_pool)
255 {
256   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
257   const char *result = NULL;
258   int i;
259 
260   for (i = 0; i < mergeinfo->nelts; ++i)
261     {
262       apr_hash_index_t *hi;
263       mergeinfo_t *entry = APR_ARRAY_IDX(mergeinfo, i, mergeinfo_t *);
264 
265       svn_pool_clear(iterpool);
266 
267       /* Make common base path cover the wc's FS path. */
268       if (result == NULL)
269         result = apr_pstrdup(result_pool, entry->fs_path);
270       else if (!svn_dirent_is_ancestor(result, entry->fs_path))
271         result = svn_dirent_get_longest_ancestor(result, entry->fs_path,
272                                                  result_pool);
273 
274       /* Cover the branch FS paths mentioned in the mergeinfo. */
275       for (hi = apr_hash_first(scratch_pool, entry->mergeinfo);
276            hi;
277            hi = apr_hash_next(hi))
278         {
279           const char * path = apr_hash_this_key(hi);
280           if (!svn_dirent_is_ancestor(result, path))
281             result = svn_dirent_get_longest_ancestor(result, path,
282                                                      result_pool);
283         }
284     }
285 
286   svn_pool_destroy(iterpool);
287   return result;
288 }
289 
290 void
svn_min__get_mergeinfo_pair(const char ** fs_path,const char ** parent_path,const char ** subtree_relpath,svn_mergeinfo_t * parent_mergeinfo,svn_mergeinfo_t * subtree_mergeinfo,apr_array_header_t ** siblings_mergeinfo,apr_array_header_t * mergeinfo,int idx)291 svn_min__get_mergeinfo_pair(const char **fs_path,
292                             const char **parent_path,
293                             const char **subtree_relpath,
294                             svn_mergeinfo_t *parent_mergeinfo,
295                             svn_mergeinfo_t *subtree_mergeinfo,
296                             apr_array_header_t **siblings_mergeinfo,
297                             apr_array_header_t *mergeinfo,
298                             int idx)
299 {
300   mergeinfo_t *entry;
301   if (idx < 0 || mergeinfo->nelts <= idx)
302     {
303       *fs_path = "";
304       *parent_path = "";
305       *subtree_relpath = "";
306       *parent_mergeinfo = NULL;
307       *subtree_mergeinfo = NULL;
308       *siblings_mergeinfo = NULL;
309 
310       return;
311     }
312 
313   entry = APR_ARRAY_IDX(mergeinfo, idx, mergeinfo_t *);
314   *fs_path = entry->fs_path;
315   *subtree_mergeinfo = entry->mergeinfo;
316 
317   if (!entry->parent)
318     {
319       *parent_path = entry->local_path;
320       *subtree_relpath = "";
321       *parent_mergeinfo = NULL;
322       *siblings_mergeinfo = NULL;
323 
324       return;
325     }
326 
327   *parent_path = entry->parent->local_path;
328   *subtree_relpath = svn_dirent_skip_ancestor(entry->parent->local_path,
329                                               entry->local_path);
330   *parent_mergeinfo = entry->parent->mergeinfo;
331   *siblings_mergeinfo = entry->parent->children;
332 }
333 
334 svn_mergeinfo_t
svn_min__get_mergeinfo(apr_array_header_t * mergeinfo,int idx)335 svn_min__get_mergeinfo(apr_array_header_t *mergeinfo,
336                        int idx)
337 {
338   SVN_ERR_ASSERT_NO_RETURN(idx >= 0 && idx < mergeinfo->nelts);
339   return APR_ARRAY_IDX(mergeinfo, idx, mergeinfo_t *)->mergeinfo;
340 }
341 
342 svn_error_t *
svn_min__sibling_ranges(apr_hash_t ** sibling_ranges,apr_array_header_t * sibling_mergeinfo,const char * parent_path,svn_rangelist_t * relevant_ranges,apr_pool_t * result_pool,apr_pool_t * scratch_pool)343 svn_min__sibling_ranges(apr_hash_t **sibling_ranges,
344                         apr_array_header_t *sibling_mergeinfo,
345                         const char *parent_path,
346                         svn_rangelist_t *relevant_ranges,
347                         apr_pool_t *result_pool,
348                         apr_pool_t *scratch_pool)
349 {
350   int i;
351   apr_hash_t *result = svn_hash__make(result_pool);
352   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
353 
354   for (i = 0; i < sibling_mergeinfo->nelts; ++i)
355     {
356       svn_mergeinfo_t mergeinfo;
357       apr_hash_index_t *hi;
358 
359       svn_pool_clear(iterpool);
360       mergeinfo = APR_ARRAY_IDX(sibling_mergeinfo, i, svn_mergeinfo_t);
361 
362       for (hi = apr_hash_first(iterpool, mergeinfo);
363            hi;
364            hi = apr_hash_next(hi))
365         {
366           const char *path = apr_hash_this_key(hi);
367           if (svn_dirent_is_ancestor(parent_path, path))
368             {
369               svn_rangelist_t *common, *ranges = apr_hash_this_val(hi);
370               SVN_ERR(svn_rangelist_intersect(&common, ranges,
371                                               relevant_ranges, TRUE,
372                                               result_pool));
373 
374               if (common->nelts)
375                 {
376                   svn_hash_sets(result, apr_pstrdup(result_pool, path),
377                                 common);
378                 }
379             }
380         }
381     }
382 
383   svn_pool_destroy(iterpool);
384   *sibling_ranges = result;
385 
386   return SVN_NO_ERROR;
387 }
388 
389 svn_error_t *
svn_min__write_mergeinfo(svn_min__cmd_baton_t * baton,apr_array_header_t * mergeinfo,apr_pool_t * scratch_pool)390 svn_min__write_mergeinfo(svn_min__cmd_baton_t *baton,
391                          apr_array_header_t *mergeinfo,
392                          apr_pool_t *scratch_pool)
393 {
394   svn_client_ctx_t *ctx = baton->ctx;
395 
396   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
397   int i;
398 
399   for (i = 0; i < mergeinfo->nelts; ++i)
400     {
401       mergeinfo_t *entry = APR_ARRAY_IDX(mergeinfo, i, mergeinfo_t *);
402       svn_string_t *propval = NULL;
403       apr_array_header_t *targets;
404 
405       svn_pool_clear(iterpool);
406 
407       targets = apr_array_make(iterpool, 1, sizeof(const char *));
408       APR_ARRAY_PUSH(targets, const char *) = entry->local_path;
409 
410       /* If the mergeinfo is empty, keep the NULL PROPVAL to actually
411        * delete the property. */
412       if (apr_hash_count(entry->mergeinfo))
413         SVN_ERR(svn_mergeinfo_to_string(&propval, entry->mergeinfo,
414                                         iterpool));
415 
416       SVN_ERR(svn_client_propset_local(SVN_PROP_MERGEINFO, propval, targets,
417                                        svn_depth_empty, FALSE, NULL, ctx,
418                                        iterpool));
419     }
420 
421   svn_pool_destroy(iterpool);
422 
423   return SVN_NO_ERROR;
424 }
425 
426 svn_error_t *
svn_min__remove_empty_mergeinfo(apr_array_header_t * mergeinfo)427 svn_min__remove_empty_mergeinfo(apr_array_header_t *mergeinfo)
428 {
429   int i;
430   int dest;
431 
432   for (i = 0, dest = 0; i < mergeinfo->nelts; ++i)
433     {
434       mergeinfo_t *entry = APR_ARRAY_IDX(mergeinfo, i, mergeinfo_t *);
435       if (apr_hash_count(entry->mergeinfo))
436         {
437           APR_ARRAY_IDX(mergeinfo, dest, mergeinfo_t *) = entry;
438           ++dest;
439         }
440     }
441 
442   mergeinfo->nelts = dest;
443 
444   return SVN_NO_ERROR;
445 }
446 
447 svn_error_t *
svn_min__print_mergeinfo_stats(apr_array_header_t * wc_mergeinfo,apr_pool_t * scratch_pool)448 svn_min__print_mergeinfo_stats(apr_array_header_t *wc_mergeinfo,
449                                apr_pool_t *scratch_pool)
450 {
451   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
452 
453   int branch_count = 0;
454   int range_count = 0;
455 
456   /* Aggregate numbers. */
457   int i;
458   for (i = 0; i < wc_mergeinfo->nelts; ++i)
459     {
460       apr_hash_index_t *hi;
461       svn_mergeinfo_t mergeinfo = svn_min__get_mergeinfo(wc_mergeinfo, i);
462 
463       svn_pool_clear(iterpool);
464 
465       branch_count += apr_hash_count(mergeinfo);
466 
467       for (hi = apr_hash_first(iterpool, mergeinfo);
468            hi;
469            hi = apr_hash_next(hi))
470         {
471           svn_rangelist_t *ranges = apr_hash_this_val(hi);
472           range_count += ranges->nelts;
473         }
474     }
475 
476   /* Show them. */
477   SVN_ERR(svn_cmdline_printf(scratch_pool,
478                              _("    Found mergeinfo on %d nodes.\n"),
479                              wc_mergeinfo->nelts));
480   SVN_ERR(svn_cmdline_printf(scratch_pool,
481                              _("    Found %d branch entries.\n"),
482                              branch_count));
483   SVN_ERR(svn_cmdline_printf(scratch_pool,
484                              _("    Found %d merged revision ranges.\n\n"),
485                              range_count));
486 
487   svn_pool_destroy(iterpool);
488 
489   return SVN_NO_ERROR;
490 }
491 
492