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