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_pools.h" 31#include "svn_client.h" 32#include "svn_cmdline.h" 33#include "svn_path.h" 34#include "svn_error.h" 35#include "svn_error_codes.h" 36#include "svn_types.h" 37#include "cl.h" 38 39#include "svn_private_config.h" 40 41 42/*** Code. ***/ 43 44/* Implements the svn_log_entry_receiver_t interface. */ 45static svn_error_t * 46print_log_rev(void *baton, 47 svn_log_entry_t *log_entry, 48 apr_pool_t *pool) 49{ 50 if (log_entry->non_inheritable) 51 SVN_ERR(svn_cmdline_printf(pool, "r%ld*\n", log_entry->revision)); 52 else 53 SVN_ERR(svn_cmdline_printf(pool, "r%ld\n", log_entry->revision)); 54 55 return SVN_NO_ERROR; 56} 57 58/* Draw a diagram (by printing text to the console) summarizing the state 59 * of merging between two branches, given the merge description 60 * indicated by YCA, BASE, RIGHT, TARGET, REINTEGRATE_LIKE. */ 61static svn_error_t * 62mergeinfo_diagram(const char *yca_url, 63 const char *base_url, 64 const char *right_url, 65 const char *target_url, 66 svn_revnum_t yca_rev, 67 svn_revnum_t base_rev, 68 svn_revnum_t right_rev, 69 svn_revnum_t target_rev, 70 const char *repos_root_url, 71 svn_boolean_t target_is_wc, 72 svn_boolean_t reintegrate_like, 73 apr_pool_t *pool) 74{ 75 /* The graph occupies 4 rows of text, and the annotations occupy 76 * another 2 rows above and 2 rows below. The graph is constructed 77 * from left to right in discrete sections ("columns"), each of which 78 * can have a different width (measured in characters). Each element in 79 * the array is either a text string of the appropriate width, or can 80 * be NULL to draw a blank cell. */ 81#define ROWS 8 82#define COLS 4 83 const char *g[ROWS][COLS] = {{0}}; 84 int col_width[COLS]; 85 int row, col; 86 87 /* The YCA (that is, the branching point). And an ellipsis, because we 88 * don't show information about earlier merges */ 89 g[0][0] = apr_psprintf(pool, " %-8ld ", yca_rev); 90 g[1][0] = " | "; 91 if (strcmp(yca_url, right_url) == 0) 92 { 93 g[2][0] = "-------| |--"; 94 g[3][0] = " \\ "; 95 g[4][0] = " \\ "; 96 g[5][0] = " --| |--"; 97 } 98 else if (strcmp(yca_url, target_url) == 0) 99 { 100 g[2][0] = " --| |--"; 101 g[3][0] = " / "; 102 g[4][0] = " / "; 103 g[5][0] = "-------| |--"; 104 } 105 else 106 { 107 g[2][0] = " --| |--"; 108 g[3][0] = "... / "; 109 g[4][0] = " \\ "; 110 g[5][0] = " --| |--"; 111 } 112 113 /* The last full merge */ 114 if ((base_rev > yca_rev) && reintegrate_like) 115 { 116 g[2][2] = "---------"; 117 g[3][2] = " / "; 118 g[4][2] = " / "; 119 g[5][2] = "---------"; 120 g[6][2] = "| "; 121 g[7][2] = apr_psprintf(pool, "%-8ld ", base_rev); 122 } 123 else if (base_rev > yca_rev) 124 { 125 g[0][2] = apr_psprintf(pool, "%-8ld ", base_rev); 126 g[1][2] = "| "; 127 g[2][2] = "---------"; 128 g[3][2] = " \\ "; 129 g[4][2] = " \\ "; 130 g[5][2] = "---------"; 131 } 132 else 133 { 134 g[2][2] = "---------"; 135 g[3][2] = " "; 136 g[4][2] = " "; 137 g[5][2] = "---------"; 138 } 139 140 /* The tips of the branches */ 141 { 142 g[0][3] = apr_psprintf(pool, "%-8ld", right_rev); 143 g[1][3] = "| "; 144 g[2][3] = "- "; 145 g[3][3] = " "; 146 g[4][3] = " "; 147 g[5][3] = "- "; 148 g[6][3] = "| "; 149 g[7][3] = target_is_wc ? "WC " 150 : apr_psprintf(pool, "%-8ld", target_rev); 151 } 152 153 /* Find the width of each column, so we know how to print blank cells */ 154 for (col = 0; col < COLS; col++) 155 { 156 col_width[col] = 0; 157 for (row = 0; row < ROWS; row++) 158 { 159 if (g[row][col] && ((int)strlen(g[row][col]) > col_width[col])) 160 col_width[col] = (int)strlen(g[row][col]); 161 } 162 } 163 164 /* Column headings */ 165 SVN_ERR(svn_cmdline_printf(pool, 166 " %s\n" 167 " | %s\n" 168 " | | %s\n" 169 " | | | %s\n" 170 "\n", 171 _("youngest common ancestor"), _("last full merge"), 172 _("tip of branch"), _("repository path"))); 173 174 /* Print the diagram, row by row */ 175 for (row = 0; row < ROWS; row++) 176 { 177 SVN_ERR(svn_cmdline_fputs(" ", stdout, pool)); 178 for (col = 0; col < COLS; col++) 179 { 180 if (g[row][col]) 181 { 182 SVN_ERR(svn_cmdline_fputs(g[row][col], stdout, pool)); 183 } 184 else 185 { 186 /* Print <column-width> spaces */ 187 SVN_ERR(svn_cmdline_printf(pool, "%*s", col_width[col], "")); 188 } 189 } 190 if (row == 2) 191 SVN_ERR(svn_cmdline_printf(pool, " %s", 192 svn_uri_skip_ancestor(repos_root_url, right_url, pool))); 193 if (row == 5) 194 SVN_ERR(svn_cmdline_printf(pool, " %s", 195 svn_uri_skip_ancestor(repos_root_url, target_url, pool))); 196 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool)); 197 } 198 199 return SVN_NO_ERROR; 200} 201 202/* Display a summary of the state of merging between the two branches 203 * SOURCE_PATH_OR_URL@SOURCE_REVISION and 204 * TARGET_PATH_OR_URL@TARGET_REVISION. */ 205static svn_error_t * 206mergeinfo_summary( 207 const char *source_path_or_url, 208 const svn_opt_revision_t *source_revision, 209 const char *target_path_or_url, 210 const svn_opt_revision_t *target_revision, 211 svn_client_ctx_t *ctx, 212 apr_pool_t *pool) 213{ 214 const char *yca_url, *base_url, *right_url, *target_url; 215 svn_revnum_t yca_rev, base_rev, right_rev, target_rev; 216 const char *repos_root_url; 217 svn_boolean_t target_is_wc, is_reintegration; 218 219 target_is_wc = (! svn_path_is_url(target_path_or_url)) 220 && (target_revision->kind == svn_opt_revision_unspecified 221 || target_revision->kind == svn_opt_revision_working); 222 SVN_ERR(svn_client_get_merging_summary( 223 &is_reintegration, 224 &yca_url, &yca_rev, 225 &base_url, &base_rev, 226 &right_url, &right_rev, 227 &target_url, &target_rev, 228 &repos_root_url, 229 source_path_or_url, source_revision, 230 target_path_or_url, target_revision, 231 ctx, pool, pool)); 232 233 SVN_ERR(mergeinfo_diagram(yca_url, base_url, right_url, target_url, 234 yca_rev, base_rev, right_rev, target_rev, 235 repos_root_url, target_is_wc, is_reintegration, 236 pool)); 237 238 return SVN_NO_ERROR; 239} 240 241/* This implements the `svn_opt_subcommand_t' interface. */ 242svn_error_t * 243svn_cl__mergeinfo(apr_getopt_t *os, 244 void *baton, 245 apr_pool_t *pool) 246{ 247 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; 248 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; 249 apr_array_header_t *targets; 250 const char *source, *target; 251 svn_opt_revision_t src_peg_revision, tgt_peg_revision; 252 svn_opt_revision_t *src_start_revision, *src_end_revision; 253 /* Default to depth empty. */ 254 svn_depth_t depth = (opt_state->depth == svn_depth_unknown) 255 ? svn_depth_empty : opt_state->depth; 256 257 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 258 opt_state->targets, 259 ctx, FALSE, pool)); 260 261 /* Parse the arguments: SOURCE[@REV] optionally followed by TARGET[@REV]. */ 262 if (targets->nelts < 1) 263 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 264 _("Not enough arguments given")); 265 if (targets->nelts > 2) 266 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 267 _("Too many arguments given")); 268 SVN_ERR(svn_opt_parse_path(&src_peg_revision, &source, 269 APR_ARRAY_IDX(targets, 0, const char *), pool)); 270 if (targets->nelts == 2) 271 { 272 SVN_ERR(svn_opt_parse_path(&tgt_peg_revision, &target, 273 APR_ARRAY_IDX(targets, 1, const char *), 274 pool)); 275 } 276 else 277 { 278 target = ""; 279 tgt_peg_revision.kind = svn_opt_revision_unspecified; 280 } 281 282 /* If no peg-rev was attached to the source URL, assume HEAD. */ 283 /* ### But what if SOURCE is a WC path not a URL -- shouldn't we then use 284 * BASE (but not WORKING: that would be inconsistent with 'svn merge')? */ 285 if (src_peg_revision.kind == svn_opt_revision_unspecified) 286 src_peg_revision.kind = svn_opt_revision_head; 287 288 /* If no peg-rev was attached to a URL target, then assume HEAD; if 289 no peg-rev was attached to a non-URL target, then assume BASE. */ 290 /* ### But we would like to be able to examine a working copy with an 291 uncommitted merge in it, so change this to use WORKING not BASE? */ 292 if (tgt_peg_revision.kind == svn_opt_revision_unspecified) 293 { 294 if (svn_path_is_url(target)) 295 tgt_peg_revision.kind = svn_opt_revision_head; 296 else 297 tgt_peg_revision.kind = svn_opt_revision_base; 298 } 299 300 src_start_revision = &(opt_state->start_revision); 301 if (opt_state->end_revision.kind == svn_opt_revision_unspecified) 302 src_end_revision = src_start_revision; 303 else 304 src_end_revision = &(opt_state->end_revision); 305 306 /* Do the real work, depending on the requested data flavor. */ 307 if (opt_state->show_revs == svn_cl__show_revs_merged) 308 { 309 apr_array_header_t *revprops; 310 311 /* We need only revisions number, not revision properties. */ 312 revprops = apr_array_make(pool, 0, sizeof(const char *)); 313 314 SVN_ERR(svn_client_mergeinfo_log2(TRUE, target, &tgt_peg_revision, 315 source, &src_peg_revision, 316 src_start_revision, 317 src_end_revision, 318 print_log_rev, NULL, 319 TRUE, depth, revprops, ctx, 320 pool)); 321 } 322 else if (opt_state->show_revs == svn_cl__show_revs_eligible) 323 { 324 apr_array_header_t *revprops; 325 326 /* We need only revisions number, not revision properties. */ 327 revprops = apr_array_make(pool, 0, sizeof(const char *)); 328 329 SVN_ERR(svn_client_mergeinfo_log2(FALSE, target, &tgt_peg_revision, 330 source, &src_peg_revision, 331 src_start_revision, 332 src_end_revision, 333 print_log_rev, NULL, 334 TRUE, depth, revprops, ctx, 335 pool)); 336 } 337 else 338 { 339 if ((opt_state->start_revision.kind != svn_opt_revision_unspecified) 340 || (opt_state->end_revision.kind != svn_opt_revision_unspecified)) 341 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 342 _("--revision (-r) option valid only with " 343 "--show-revs option")); 344 if (opt_state->depth != svn_depth_unknown) 345 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 346 _("Depth specification options valid only " 347 "with --show-revs option")); 348 349 SVN_ERR(mergeinfo_summary(source, &src_peg_revision, 350 target, &tgt_peg_revision, 351 ctx, pool)); 352 } 353 return SVN_NO_ERROR; 354} 355