2 * mergeinfo-cmd.c -- Query merge-relative info.
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 /* ==================================================================== */
30 #include "svn_compat.h"
31 #include "svn_pools.h"
32 #include "svn_props.h"
33 #include "svn_client.h"
34 #include "svn_cmdline.h"
36 #include "svn_error.h"
37 #include "svn_error_codes.h"
38 #include "svn_types.h"
42 #include "svn_private_config.h"
47 /* Implements the svn_log_entry_receiver_t interface. */
49 print_log_rev(void *baton,
50 svn_log_entry_t *log_entry,
53 if (log_entry->non_inheritable)
54 SVN_ERR(svn_cmdline_printf(pool, "r%ld*\n", log_entry->revision));
56 SVN_ERR(svn_cmdline_printf(pool, "r%ld\n", log_entry->revision));
61 /* Implements a svn_log_entry_receiver_t interface that filters out changed
62 * paths data before calling the svn_cl__log_entry_receiver(). Right now we
63 * always have to pass TRUE for discover_changed_paths for
64 * svn_client_mergeinfo_log2() due to the side effect of that option. The
65 * svn_cl__log_entry_receiver() discovers if it should print the changed paths
66 * implicitly by the path info existing. As a result this filter is needed
67 * to allow expected output without changed paths.
70 print_log_details(void *baton,
71 svn_log_entry_t *log_entry,
74 log_entry->changed_paths = NULL;
75 log_entry->changed_paths2 = NULL;
77 return svn_cl__log_entry_receiver(baton, log_entry, pool);
80 /* Draw a diagram (by printing text to the console) summarizing the state
81 * of merging between two branches, given the merge description
82 * indicated by YCA, BASE, RIGHT, TARGET, REINTEGRATE_LIKE. */
84 mergeinfo_diagram(const char *yca_url,
86 const char *right_url,
87 const char *target_url,
89 svn_revnum_t base_rev,
90 svn_revnum_t right_rev,
91 svn_revnum_t target_rev,
92 const char *repos_root_url,
93 svn_boolean_t target_is_wc,
94 svn_boolean_t reintegrate_like,
97 /* The graph occupies 4 rows of text, and the annotations occupy
98 * another 2 rows above and 2 rows below. The graph is constructed
99 * from left to right in discrete sections ("columns"), each of which
100 * can have a different width (measured in characters). Each element in
101 * the array is either a text string of the appropriate width, or can
102 * be NULL to draw a blank cell. */
105 const char *g[ROWS][COLS] = {{0}};
109 /* The YCA (that is, the branching point). And an ellipsis, because we
110 * don't show information about earlier merges */
111 g[0][0] = apr_psprintf(pool, " %-8ld ", yca_rev);
113 if (strcmp(yca_url, right_url) == 0)
115 g[2][0] = "-------| |--";
118 g[5][0] = " --| |--";
120 else if (strcmp(yca_url, target_url) == 0)
122 g[2][0] = " --| |--";
125 g[5][0] = "-------| |--";
129 g[2][0] = " --| |--";
132 g[5][0] = " --| |--";
135 /* The last full merge */
136 if ((base_rev > yca_rev) && reintegrate_like)
138 g[2][2] = "---------";
141 g[5][2] = "---------";
143 g[7][2] = apr_psprintf(pool, "%-8ld ", base_rev);
145 else if (base_rev > yca_rev)
147 g[0][2] = apr_psprintf(pool, "%-8ld ", base_rev);
149 g[2][2] = "---------";
152 g[5][2] = "---------";
156 g[2][2] = "---------";
159 g[5][2] = "---------";
162 /* The tips of the branches */
164 g[0][3] = apr_psprintf(pool, "%-8ld", right_rev);
171 g[7][3] = target_is_wc ? "WC "
172 : apr_psprintf(pool, "%-8ld", target_rev);
175 /* Find the width of each column, so we know how to print blank cells */
176 for (col = 0; col < COLS; col++)
179 for (row = 0; row < ROWS; row++)
181 if (g[row][col] && ((int)strlen(g[row][col]) > col_width[col]))
182 col_width[col] = (int)strlen(g[row][col]);
186 /* Column headings */
187 SVN_ERR(svn_cmdline_printf(pool,
193 _("youngest common ancestor"), _("last full merge"),
194 _("tip of branch"), _("repository path")));
196 /* Print the diagram, row by row */
197 for (row = 0; row < ROWS; row++)
199 SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
200 for (col = 0; col < COLS; col++)
204 SVN_ERR(svn_cmdline_fputs(g[row][col], stdout, pool));
208 /* Print <column-width> spaces */
209 SVN_ERR(svn_cmdline_printf(pool, "%*s", col_width[col], ""));
213 SVN_ERR(svn_cmdline_printf(pool, " %s",
214 svn_uri_skip_ancestor(repos_root_url, right_url, pool)));
216 SVN_ERR(svn_cmdline_printf(pool, " %s",
217 svn_uri_skip_ancestor(repos_root_url, target_url, pool)));
218 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
224 /* Display a summary of the state of merging between the two branches
225 * SOURCE_PATH_OR_URL@SOURCE_REVISION and
226 * TARGET_PATH_OR_URL@TARGET_REVISION. */
229 const char *source_path_or_url,
230 const svn_opt_revision_t *source_revision,
231 const char *target_path_or_url,
232 const svn_opt_revision_t *target_revision,
233 svn_client_ctx_t *ctx,
236 const char *yca_url, *base_url, *right_url, *target_url;
237 svn_revnum_t yca_rev, base_rev, right_rev, target_rev;
238 const char *repos_root_url;
239 svn_boolean_t target_is_wc, is_reintegration;
241 target_is_wc = (! svn_path_is_url(target_path_or_url))
242 && (target_revision->kind == svn_opt_revision_unspecified
243 || target_revision->kind == svn_opt_revision_working
244 || target_revision->kind == svn_opt_revision_base);
245 SVN_ERR(svn_client_get_merging_summary(
248 &base_url, &base_rev,
249 &right_url, &right_rev,
250 &target_url, &target_rev,
252 source_path_or_url, source_revision,
253 target_path_or_url, target_revision,
256 SVN_ERR(mergeinfo_diagram(yca_url, base_url, right_url, target_url,
257 yca_rev, base_rev, right_rev, target_rev,
258 repos_root_url, target_is_wc, is_reintegration,
265 mergeinfo_log(svn_boolean_t finding_merged,
267 const svn_opt_revision_t *tgt_peg_revision,
269 const svn_opt_revision_t *src_peg_revision,
270 const svn_opt_revision_t *src_start_revision,
271 const svn_opt_revision_t *src_end_revision,
273 svn_boolean_t include_log_details,
275 svn_boolean_t verbose,
276 svn_boolean_t incremental,
277 svn_client_ctx_t *ctx,
280 apr_array_header_t *revprops;
281 svn_log_entry_receiver_t log_receiver;
282 void *log_receiver_baton;
284 if (include_log_details)
286 svn_cl__log_receiver_baton *baton;
288 revprops = apr_array_make(pool, 3, sizeof(const char *));
289 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
290 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
292 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
295 log_receiver = svn_cl__log_entry_receiver;
297 log_receiver = print_log_details;
299 baton = apr_palloc(pool, sizeof(svn_cl__log_receiver_baton));
301 baton->target_path_or_url = target;
302 baton->target_peg_revision = *tgt_peg_revision;
303 baton->omit_log_message = quiet;
304 baton->show_diff = FALSE;
305 baton->depth = depth;
306 baton->diff_extensions = NULL;
307 baton->merge_stack = NULL;
308 baton->search_patterns = NULL;
310 log_receiver_baton = baton;
314 /* We need only revisions number, not revision properties. */
315 revprops = apr_array_make(pool, 0, sizeof(const char *));
316 log_receiver = print_log_rev;
317 log_receiver_baton = NULL;
320 SVN_ERR(svn_client_mergeinfo_log2(finding_merged, target,
322 source, src_peg_revision,
325 log_receiver, log_receiver_baton,
326 TRUE, depth, revprops, ctx,
329 if (include_log_details && !incremental)
330 SVN_ERR(svn_cmdline_printf(pool, SVN_CL__LOG_SEP_STRING));
335 /* This implements the `svn_opt_subcommand_t' interface. */
337 svn_cl__mergeinfo(apr_getopt_t *os,
341 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
342 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
343 apr_array_header_t *targets;
344 const char *source, *target;
345 svn_opt_revision_t src_peg_revision, tgt_peg_revision;
346 svn_opt_revision_t *src_start_revision, *src_end_revision;
347 /* Default to depth empty. */
348 svn_depth_t depth = (opt_state->depth == svn_depth_unknown)
349 ? svn_depth_empty : opt_state->depth;
351 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
355 /* Parse the arguments: SOURCE[@REV] optionally followed by TARGET[@REV]. */
356 if (targets->nelts < 1)
357 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
358 _("Not enough arguments given"));
359 if (targets->nelts > 2)
360 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
361 _("Too many arguments given"));
362 SVN_ERR(svn_opt_parse_path(&src_peg_revision, &source,
363 APR_ARRAY_IDX(targets, 0, const char *), pool));
364 if (targets->nelts == 2)
366 SVN_ERR(svn_opt_parse_path(&tgt_peg_revision, &target,
367 APR_ARRAY_IDX(targets, 1, const char *),
373 tgt_peg_revision.kind = svn_opt_revision_unspecified;
376 /* If no peg-rev was attached to the source URL, assume HEAD. */
377 /* ### But what if SOURCE is a WC path not a URL -- shouldn't we then use
378 * BASE (but not WORKING: that would be inconsistent with 'svn merge')? */
379 if (src_peg_revision.kind == svn_opt_revision_unspecified)
380 src_peg_revision.kind = svn_opt_revision_head;
382 /* If no peg-rev was attached to a URL target, then assume HEAD; if
383 no peg-rev was attached to a non-URL target, then assume BASE. */
384 /* ### But we would like to be able to examine a working copy with an
385 uncommitted merge in it, so change this to use WORKING not BASE? */
386 if (tgt_peg_revision.kind == svn_opt_revision_unspecified)
388 if (svn_path_is_url(target))
389 tgt_peg_revision.kind = svn_opt_revision_head;
391 tgt_peg_revision.kind = svn_opt_revision_base;
394 src_start_revision = &(opt_state->start_revision);
395 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
396 src_end_revision = src_start_revision;
398 src_end_revision = &(opt_state->end_revision);
400 if (!opt_state->mergeinfo_log)
402 if (opt_state->quiet)
403 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
404 _("--quiet (-q) option valid only with --log "
407 if (opt_state->verbose)
408 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
409 _("--verbose (-v) option valid only with "
412 if (opt_state->incremental)
413 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
414 _("--incremental option valid only with "
418 /* Do the real work, depending on the requested data flavor. */
419 if (opt_state->show_revs == svn_cl__show_revs_merged)
421 SVN_ERR(mergeinfo_log(TRUE, target, &tgt_peg_revision,
422 source, &src_peg_revision,
425 depth, opt_state->mergeinfo_log,
426 opt_state->quiet, opt_state->verbose,
427 opt_state->incremental, ctx, pool));
429 else if (opt_state->show_revs == svn_cl__show_revs_eligible)
431 SVN_ERR(mergeinfo_log(FALSE, target, &tgt_peg_revision,
432 source, &src_peg_revision,
435 depth, opt_state->mergeinfo_log,
436 opt_state->quiet, opt_state->verbose,
437 opt_state->incremental, ctx, pool));
441 if ((opt_state->start_revision.kind != svn_opt_revision_unspecified)
442 || (opt_state->end_revision.kind != svn_opt_revision_unspecified))
443 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
444 _("--revision (-r) option valid only with "
445 "--show-revs option"));
446 if (opt_state->depth != svn_depth_unknown)
447 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
448 _("Depth specification options valid only "
449 "with --show-revs option"));
450 if (opt_state->mergeinfo_log)
451 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
452 _("--log option valid only with "
453 "--show-revs option"));
456 SVN_ERR(mergeinfo_summary(source, &src_peg_revision,
457 target, &tgt_peg_revision,