/* * mergeinfo-cmd.c -- Query merge-relative info. * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== */ /* ==================================================================== */ /*** Includes. ***/ #include "svn_pools.h" #include "svn_client.h" #include "svn_cmdline.h" #include "svn_path.h" #include "svn_error.h" #include "svn_error_codes.h" #include "svn_types.h" #include "cl.h" #include "svn_private_config.h" /*** Code. ***/ /* Implements the svn_log_entry_receiver_t interface. */ static svn_error_t * print_log_rev(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool) { if (log_entry->non_inheritable) SVN_ERR(svn_cmdline_printf(pool, "r%ld*\n", log_entry->revision)); else SVN_ERR(svn_cmdline_printf(pool, "r%ld\n", log_entry->revision)); return SVN_NO_ERROR; } /* Draw a diagram (by printing text to the console) summarizing the state * of merging between two branches, given the merge description * indicated by YCA, BASE, RIGHT, TARGET, REINTEGRATE_LIKE. */ static svn_error_t * mergeinfo_diagram(const char *yca_url, const char *base_url, const char *right_url, const char *target_url, svn_revnum_t yca_rev, svn_revnum_t base_rev, svn_revnum_t right_rev, svn_revnum_t target_rev, const char *repos_root_url, svn_boolean_t target_is_wc, svn_boolean_t reintegrate_like, apr_pool_t *pool) { /* The graph occupies 4 rows of text, and the annotations occupy * another 2 rows above and 2 rows below. The graph is constructed * from left to right in discrete sections ("columns"), each of which * can have a different width (measured in characters). Each element in * the array is either a text string of the appropriate width, or can * be NULL to draw a blank cell. */ #define ROWS 8 #define COLS 4 const char *g[ROWS][COLS] = {{0}}; int col_width[COLS]; int row, col; /* The YCA (that is, the branching point). And an ellipsis, because we * don't show information about earlier merges */ g[0][0] = apr_psprintf(pool, " %-8ld ", yca_rev); g[1][0] = " | "; if (strcmp(yca_url, right_url) == 0) { g[2][0] = "-------| |--"; g[3][0] = " \\ "; g[4][0] = " \\ "; g[5][0] = " --| |--"; } else if (strcmp(yca_url, target_url) == 0) { g[2][0] = " --| |--"; g[3][0] = " / "; g[4][0] = " / "; g[5][0] = "-------| |--"; } else { g[2][0] = " --| |--"; g[3][0] = "... / "; g[4][0] = " \\ "; g[5][0] = " --| |--"; } /* The last full merge */ if ((base_rev > yca_rev) && reintegrate_like) { g[2][2] = "---------"; g[3][2] = " / "; g[4][2] = " / "; g[5][2] = "---------"; g[6][2] = "| "; g[7][2] = apr_psprintf(pool, "%-8ld ", base_rev); } else if (base_rev > yca_rev) { g[0][2] = apr_psprintf(pool, "%-8ld ", base_rev); g[1][2] = "| "; g[2][2] = "---------"; g[3][2] = " \\ "; g[4][2] = " \\ "; g[5][2] = "---------"; } else { g[2][2] = "---------"; g[3][2] = " "; g[4][2] = " "; g[5][2] = "---------"; } /* The tips of the branches */ { g[0][3] = apr_psprintf(pool, "%-8ld", right_rev); g[1][3] = "| "; g[2][3] = "- "; g[3][3] = " "; g[4][3] = " "; g[5][3] = "- "; g[6][3] = "| "; g[7][3] = target_is_wc ? "WC " : apr_psprintf(pool, "%-8ld", target_rev); } /* Find the width of each column, so we know how to print blank cells */ for (col = 0; col < COLS; col++) { col_width[col] = 0; for (row = 0; row < ROWS; row++) { if (g[row][col] && ((int)strlen(g[row][col]) > col_width[col])) col_width[col] = (int)strlen(g[row][col]); } } /* Column headings */ SVN_ERR(svn_cmdline_printf(pool, " %s\n" " | %s\n" " | | %s\n" " | | | %s\n" "\n", _("youngest common ancestor"), _("last full merge"), _("tip of branch"), _("repository path"))); /* Print the diagram, row by row */ for (row = 0; row < ROWS; row++) { SVN_ERR(svn_cmdline_fputs(" ", stdout, pool)); for (col = 0; col < COLS; col++) { if (g[row][col]) { SVN_ERR(svn_cmdline_fputs(g[row][col], stdout, pool)); } else { /* Print spaces */ SVN_ERR(svn_cmdline_printf(pool, "%*s", col_width[col], "")); } } if (row == 2) SVN_ERR(svn_cmdline_printf(pool, " %s", svn_uri_skip_ancestor(repos_root_url, right_url, pool))); if (row == 5) SVN_ERR(svn_cmdline_printf(pool, " %s", svn_uri_skip_ancestor(repos_root_url, target_url, pool))); SVN_ERR(svn_cmdline_fputs("\n", stdout, pool)); } return SVN_NO_ERROR; } /* Display a summary of the state of merging between the two branches * SOURCE_PATH_OR_URL@SOURCE_REVISION and * TARGET_PATH_OR_URL@TARGET_REVISION. */ static svn_error_t * mergeinfo_summary( const char *source_path_or_url, const svn_opt_revision_t *source_revision, const char *target_path_or_url, const svn_opt_revision_t *target_revision, svn_client_ctx_t *ctx, apr_pool_t *pool) { const char *yca_url, *base_url, *right_url, *target_url; svn_revnum_t yca_rev, base_rev, right_rev, target_rev; const char *repos_root_url; svn_boolean_t target_is_wc, is_reintegration; target_is_wc = (! svn_path_is_url(target_path_or_url)) && (target_revision->kind == svn_opt_revision_unspecified || target_revision->kind == svn_opt_revision_working); SVN_ERR(svn_client_get_merging_summary( &is_reintegration, &yca_url, &yca_rev, &base_url, &base_rev, &right_url, &right_rev, &target_url, &target_rev, &repos_root_url, source_path_or_url, source_revision, target_path_or_url, target_revision, ctx, pool, pool)); SVN_ERR(mergeinfo_diagram(yca_url, base_url, right_url, target_url, yca_rev, base_rev, right_rev, target_rev, repos_root_url, target_is_wc, is_reintegration, pool)); return SVN_NO_ERROR; } /* This implements the `svn_opt_subcommand_t' interface. */ svn_error_t * svn_cl__mergeinfo(apr_getopt_t *os, void *baton, apr_pool_t *pool) { svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; apr_array_header_t *targets; const char *source, *target; svn_opt_revision_t src_peg_revision, tgt_peg_revision; svn_opt_revision_t *src_start_revision, *src_end_revision; /* Default to depth empty. */ svn_depth_t depth = (opt_state->depth == svn_depth_unknown) ? svn_depth_empty : opt_state->depth; SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, ctx, FALSE, pool)); /* Parse the arguments: SOURCE[@REV] optionally followed by TARGET[@REV]. */ if (targets->nelts < 1) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Not enough arguments given")); if (targets->nelts > 2) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Too many arguments given")); SVN_ERR(svn_opt_parse_path(&src_peg_revision, &source, APR_ARRAY_IDX(targets, 0, const char *), pool)); if (targets->nelts == 2) { SVN_ERR(svn_opt_parse_path(&tgt_peg_revision, &target, APR_ARRAY_IDX(targets, 1, const char *), pool)); } else { target = ""; tgt_peg_revision.kind = svn_opt_revision_unspecified; } /* If no peg-rev was attached to the source URL, assume HEAD. */ /* ### But what if SOURCE is a WC path not a URL -- shouldn't we then use * BASE (but not WORKING: that would be inconsistent with 'svn merge')? */ if (src_peg_revision.kind == svn_opt_revision_unspecified) src_peg_revision.kind = svn_opt_revision_head; /* If no peg-rev was attached to a URL target, then assume HEAD; if no peg-rev was attached to a non-URL target, then assume BASE. */ /* ### But we would like to be able to examine a working copy with an uncommitted merge in it, so change this to use WORKING not BASE? */ if (tgt_peg_revision.kind == svn_opt_revision_unspecified) { if (svn_path_is_url(target)) tgt_peg_revision.kind = svn_opt_revision_head; else tgt_peg_revision.kind = svn_opt_revision_base; } src_start_revision = &(opt_state->start_revision); if (opt_state->end_revision.kind == svn_opt_revision_unspecified) src_end_revision = src_start_revision; else src_end_revision = &(opt_state->end_revision); /* Do the real work, depending on the requested data flavor. */ if (opt_state->show_revs == svn_cl__show_revs_merged) { apr_array_header_t *revprops; /* We need only revisions number, not revision properties. */ revprops = apr_array_make(pool, 0, sizeof(const char *)); SVN_ERR(svn_client_mergeinfo_log2(TRUE, target, &tgt_peg_revision, source, &src_peg_revision, src_start_revision, src_end_revision, print_log_rev, NULL, TRUE, depth, revprops, ctx, pool)); } else if (opt_state->show_revs == svn_cl__show_revs_eligible) { apr_array_header_t *revprops; /* We need only revisions number, not revision properties. */ revprops = apr_array_make(pool, 0, sizeof(const char *)); SVN_ERR(svn_client_mergeinfo_log2(FALSE, target, &tgt_peg_revision, source, &src_peg_revision, src_start_revision, src_end_revision, print_log_rev, NULL, TRUE, depth, revprops, ctx, pool)); } else { if ((opt_state->start_revision.kind != svn_opt_revision_unspecified) || (opt_state->end_revision.kind != svn_opt_revision_unspecified)) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("--revision (-r) option valid only with " "--show-revs option")); if (opt_state->depth != svn_depth_unknown) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Depth specification options valid only " "with --show-revs option")); SVN_ERR(mergeinfo_summary(source, &src_peg_revision, target, &tgt_peg_revision, ctx, pool)); } return SVN_NO_ERROR; }