mergeinfo-cmd.c revision 299742
1/* 2 * mergeinfo-cmd.c -- Query merge-relative info. 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_compat.h" 31#include "svn_pools.h" 32#include "svn_props.h" 33#include "svn_client.h" 34#include "svn_cmdline.h" 35#include "svn_path.h" 36#include "svn_error.h" 37#include "svn_error_codes.h" 38#include "svn_types.h" 39#include "cl.h" 40#include "cl-log.h" 41 42#include "svn_private_config.h" 43 44 45/*** Code. ***/ 46 47/* Implements the svn_log_entry_receiver_t interface. */ 48static svn_error_t * 49print_log_rev(void *baton, 50 svn_log_entry_t *log_entry, 51 apr_pool_t *pool) 52{ 53 if (log_entry->non_inheritable) 54 SVN_ERR(svn_cmdline_printf(pool, "r%ld*\n", log_entry->revision)); 55 else 56 SVN_ERR(svn_cmdline_printf(pool, "r%ld\n", log_entry->revision)); 57 58 return SVN_NO_ERROR; 59} 60 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. 68 */ 69static svn_error_t * 70print_log_details(void *baton, 71 svn_log_entry_t *log_entry, 72 apr_pool_t *pool) 73{ 74 log_entry->changed_paths = NULL; 75 log_entry->changed_paths2 = NULL; 76 77 return svn_cl__log_entry_receiver(baton, log_entry, pool); 78} 79 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. */ 83static svn_error_t * 84mergeinfo_diagram(const char *yca_url, 85 const char *base_url, 86 const char *right_url, 87 const char *target_url, 88 svn_revnum_t yca_rev, 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, 95 apr_pool_t *pool) 96{ 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. */ 103#define ROWS 8 104#define COLS 4 105 const char *g[ROWS][COLS] = {{0}}; 106 int col_width[COLS]; 107 int row, col; 108 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); 112 g[1][0] = " | "; 113 if (strcmp(yca_url, right_url) == 0) 114 { 115 g[2][0] = "-------| |--"; 116 g[3][0] = " \\ "; 117 g[4][0] = " \\ "; 118 g[5][0] = " --| |--"; 119 } 120 else if (strcmp(yca_url, target_url) == 0) 121 { 122 g[2][0] = " --| |--"; 123 g[3][0] = " / "; 124 g[4][0] = " / "; 125 g[5][0] = "-------| |--"; 126 } 127 else 128 { 129 g[2][0] = " --| |--"; 130 g[3][0] = "... / "; 131 g[4][0] = " \\ "; 132 g[5][0] = " --| |--"; 133 } 134 135 /* The last full merge */ 136 if ((base_rev > yca_rev) && reintegrate_like) 137 { 138 g[2][2] = "---------"; 139 g[3][2] = " / "; 140 g[4][2] = " / "; 141 g[5][2] = "---------"; 142 g[6][2] = "| "; 143 g[7][2] = apr_psprintf(pool, "%-8ld ", base_rev); 144 } 145 else if (base_rev > yca_rev) 146 { 147 g[0][2] = apr_psprintf(pool, "%-8ld ", base_rev); 148 g[1][2] = "| "; 149 g[2][2] = "---------"; 150 g[3][2] = " \\ "; 151 g[4][2] = " \\ "; 152 g[5][2] = "---------"; 153 } 154 else 155 { 156 g[2][2] = "---------"; 157 g[3][2] = " "; 158 g[4][2] = " "; 159 g[5][2] = "---------"; 160 } 161 162 /* The tips of the branches */ 163 { 164 g[0][3] = apr_psprintf(pool, "%-8ld", right_rev); 165 g[1][3] = "| "; 166 g[2][3] = "- "; 167 g[3][3] = " "; 168 g[4][3] = " "; 169 g[5][3] = "- "; 170 g[6][3] = "| "; 171 g[7][3] = target_is_wc ? "WC " 172 : apr_psprintf(pool, "%-8ld", target_rev); 173 } 174 175 /* Find the width of each column, so we know how to print blank cells */ 176 for (col = 0; col < COLS; col++) 177 { 178 col_width[col] = 0; 179 for (row = 0; row < ROWS; row++) 180 { 181 if (g[row][col] && ((int)strlen(g[row][col]) > col_width[col])) 182 col_width[col] = (int)strlen(g[row][col]); 183 } 184 } 185 186 /* Column headings */ 187 SVN_ERR(svn_cmdline_printf(pool, 188 " %s\n" 189 " | %s\n" 190 " | | %s\n" 191 " | | | %s\n" 192 "\n", 193 _("youngest common ancestor"), _("last full merge"), 194 _("tip of branch"), _("repository path"))); 195 196 /* Print the diagram, row by row */ 197 for (row = 0; row < ROWS; row++) 198 { 199 SVN_ERR(svn_cmdline_fputs(" ", stdout, pool)); 200 for (col = 0; col < COLS; col++) 201 { 202 if (g[row][col]) 203 { 204 SVN_ERR(svn_cmdline_fputs(g[row][col], stdout, pool)); 205 } 206 else 207 { 208 /* Print <column-width> spaces */ 209 SVN_ERR(svn_cmdline_printf(pool, "%*s", col_width[col], "")); 210 } 211 } 212 if (row == 2) 213 SVN_ERR(svn_cmdline_printf(pool, " %s", 214 svn_uri_skip_ancestor(repos_root_url, right_url, pool))); 215 if (row == 5) 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)); 219 } 220 221 return SVN_NO_ERROR; 222} 223 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. */ 227static svn_error_t * 228mergeinfo_summary( 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, 234 apr_pool_t *pool) 235{ 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; 240 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( 246 &is_reintegration, 247 &yca_url, &yca_rev, 248 &base_url, &base_rev, 249 &right_url, &right_rev, 250 &target_url, &target_rev, 251 &repos_root_url, 252 source_path_or_url, source_revision, 253 target_path_or_url, target_revision, 254 ctx, pool, pool)); 255 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, 259 pool)); 260 261 return SVN_NO_ERROR; 262} 263 264static svn_error_t * 265mergeinfo_log(svn_boolean_t finding_merged, 266 const char *target, 267 const svn_opt_revision_t *tgt_peg_revision, 268 const char *source, 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, 272 svn_depth_t depth, 273 svn_boolean_t include_log_details, 274 svn_boolean_t quiet, 275 svn_boolean_t verbose, 276 svn_boolean_t incremental, 277 svn_client_ctx_t *ctx, 278 apr_pool_t *pool) 279{ 280 apr_array_header_t *revprops; 281 svn_log_entry_receiver_t log_receiver; 282 void *log_receiver_baton; 283 284 if (include_log_details) 285 { 286 svn_cl__log_receiver_baton *baton; 287 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; 291 if (!quiet) 292 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG; 293 294 if (verbose) 295 log_receiver = svn_cl__log_entry_receiver; 296 else 297 log_receiver = print_log_details; 298 299 baton = apr_palloc(pool, sizeof(svn_cl__log_receiver_baton)); 300 baton->ctx = ctx; 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; 309 baton->pool = pool; 310 log_receiver_baton = baton; 311 } 312 else 313 { 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; 318 } 319 320 SVN_ERR(svn_client_mergeinfo_log2(finding_merged, target, 321 tgt_peg_revision, 322 source, src_peg_revision, 323 src_start_revision, 324 src_end_revision, 325 log_receiver, log_receiver_baton, 326 TRUE, depth, revprops, ctx, 327 pool)); 328 329 if (include_log_details && !incremental) 330 SVN_ERR(svn_cmdline_printf(pool, SVN_CL__LOG_SEP_STRING)); 331 332 return SVN_NO_ERROR; 333} 334 335/* This implements the `svn_opt_subcommand_t' interface. */ 336svn_error_t * 337svn_cl__mergeinfo(apr_getopt_t *os, 338 void *baton, 339 apr_pool_t *pool) 340{ 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; 350 351 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 352 opt_state->targets, 353 ctx, FALSE, pool)); 354 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) 365 { 366 SVN_ERR(svn_opt_parse_path(&tgt_peg_revision, &target, 367 APR_ARRAY_IDX(targets, 1, const char *), 368 pool)); 369 } 370 else 371 { 372 target = ""; 373 tgt_peg_revision.kind = svn_opt_revision_unspecified; 374 } 375 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; 381 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) 387 { 388 if (svn_path_is_url(target)) 389 tgt_peg_revision.kind = svn_opt_revision_head; 390 else 391 tgt_peg_revision.kind = svn_opt_revision_base; 392 } 393 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; 397 else 398 src_end_revision = &(opt_state->end_revision); 399 400 if (!opt_state->mergeinfo_log) 401 { 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 " 405 "option")); 406 407 if (opt_state->verbose) 408 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 409 _("--verbose (-v) option valid only with " 410 "--log option")); 411 412 if (opt_state->incremental) 413 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 414 _("--incremental option valid only with " 415 "--log option")); 416 } 417 418 /* Do the real work, depending on the requested data flavor. */ 419 if (opt_state->show_revs == svn_cl__show_revs_merged) 420 { 421 SVN_ERR(mergeinfo_log(TRUE, target, &tgt_peg_revision, 422 source, &src_peg_revision, 423 src_start_revision, 424 src_end_revision, 425 depth, opt_state->mergeinfo_log, 426 opt_state->quiet, opt_state->verbose, 427 opt_state->incremental, ctx, pool)); 428 } 429 else if (opt_state->show_revs == svn_cl__show_revs_eligible) 430 { 431 SVN_ERR(mergeinfo_log(FALSE, target, &tgt_peg_revision, 432 source, &src_peg_revision, 433 src_start_revision, 434 src_end_revision, 435 depth, opt_state->mergeinfo_log, 436 opt_state->quiet, opt_state->verbose, 437 opt_state->incremental, ctx, pool)); 438 } 439 else 440 { 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")); 454 455 456 SVN_ERR(mergeinfo_summary(source, &src_peg_revision, 457 target, &tgt_peg_revision, 458 ctx, pool)); 459 } 460 return SVN_NO_ERROR; 461} 462