1251881Speter/*
2251881Speter * mergeinfo-cmd.c -- Query merge-relative info.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include "svn_pools.h"
31251881Speter#include "svn_client.h"
32251881Speter#include "svn_cmdline.h"
33251881Speter#include "svn_path.h"
34251881Speter#include "svn_error.h"
35251881Speter#include "svn_error_codes.h"
36251881Speter#include "svn_types.h"
37251881Speter#include "cl.h"
38251881Speter
39251881Speter#include "svn_private_config.h"
40251881Speter
41251881Speter
42251881Speter/*** Code. ***/
43251881Speter
44251881Speter/* Implements the svn_log_entry_receiver_t interface. */
45251881Speterstatic svn_error_t *
46251881Speterprint_log_rev(void *baton,
47251881Speter              svn_log_entry_t *log_entry,
48251881Speter              apr_pool_t *pool)
49251881Speter{
50251881Speter  if (log_entry->non_inheritable)
51251881Speter    SVN_ERR(svn_cmdline_printf(pool, "r%ld*\n", log_entry->revision));
52251881Speter  else
53251881Speter    SVN_ERR(svn_cmdline_printf(pool, "r%ld\n", log_entry->revision));
54251881Speter
55251881Speter  return SVN_NO_ERROR;
56251881Speter}
57251881Speter
58251881Speter/* Draw a diagram (by printing text to the console) summarizing the state
59251881Speter * of merging between two branches, given the merge description
60251881Speter * indicated by YCA, BASE, RIGHT, TARGET, REINTEGRATE_LIKE. */
61251881Speterstatic svn_error_t *
62251881Spetermergeinfo_diagram(const char *yca_url,
63251881Speter                  const char *base_url,
64251881Speter                  const char *right_url,
65251881Speter                  const char *target_url,
66251881Speter                  svn_revnum_t yca_rev,
67251881Speter                  svn_revnum_t base_rev,
68251881Speter                  svn_revnum_t right_rev,
69251881Speter                  svn_revnum_t target_rev,
70251881Speter                  const char *repos_root_url,
71251881Speter                  svn_boolean_t target_is_wc,
72251881Speter                  svn_boolean_t reintegrate_like,
73251881Speter                  apr_pool_t *pool)
74251881Speter{
75251881Speter  /* The graph occupies 4 rows of text, and the annotations occupy
76251881Speter   * another 2 rows above and 2 rows below.  The graph is constructed
77251881Speter   * from left to right in discrete sections ("columns"), each of which
78251881Speter   * can have a different width (measured in characters).  Each element in
79251881Speter   * the array is either a text string of the appropriate width, or can
80251881Speter   * be NULL to draw a blank cell. */
81251881Speter#define ROWS 8
82251881Speter#define COLS 4
83251881Speter  const char *g[ROWS][COLS] = {{0}};
84251881Speter  int col_width[COLS];
85251881Speter  int row, col;
86251881Speter
87251881Speter  /* The YCA (that is, the branching point).  And an ellipsis, because we
88251881Speter   * don't show information about earlier merges */
89251881Speter  g[0][0] = apr_psprintf(pool, "  %-8ld  ", yca_rev);
90251881Speter  g[1][0] =     "  |         ";
91251881Speter  if (strcmp(yca_url, right_url) == 0)
92251881Speter    {
93251881Speter      g[2][0] = "-------| |--";
94251881Speter      g[3][0] = "   \\        ";
95251881Speter      g[4][0] = "    \\       ";
96251881Speter      g[5][0] = "     --| |--";
97251881Speter    }
98251881Speter  else if (strcmp(yca_url, target_url) == 0)
99251881Speter    {
100251881Speter      g[2][0] = "     --| |--";
101251881Speter      g[3][0] = "    /       ";
102251881Speter      g[4][0] = "   /        ";
103251881Speter      g[5][0] = "-------| |--";
104251881Speter    }
105251881Speter  else
106251881Speter    {
107251881Speter      g[2][0] = "     --| |--";
108251881Speter      g[3][0] = "... /       ";
109251881Speter      g[4][0] = "    \\       ";
110251881Speter      g[5][0] = "     --| |--";
111251881Speter    }
112251881Speter
113251881Speter  /* The last full merge */
114251881Speter  if ((base_rev > yca_rev) && reintegrate_like)
115251881Speter    {
116251881Speter      g[2][2] = "---------";
117251881Speter      g[3][2] = "  /      ";
118251881Speter      g[4][2] = " /       ";
119251881Speter      g[5][2] = "---------";
120251881Speter      g[6][2] = "|        ";
121251881Speter      g[7][2] = apr_psprintf(pool, "%-8ld ", base_rev);
122251881Speter    }
123251881Speter  else if (base_rev > yca_rev)
124251881Speter    {
125251881Speter      g[0][2] = apr_psprintf(pool, "%-8ld ", base_rev);
126251881Speter      g[1][2] = "|        ";
127251881Speter      g[2][2] = "---------";
128251881Speter      g[3][2] = " \\       ";
129251881Speter      g[4][2] = "  \\      ";
130251881Speter      g[5][2] = "---------";
131251881Speter    }
132251881Speter  else
133251881Speter    {
134251881Speter      g[2][2] = "---------";
135251881Speter      g[3][2] = "         ";
136251881Speter      g[4][2] = "         ";
137251881Speter      g[5][2] = "---------";
138251881Speter    }
139251881Speter
140251881Speter  /* The tips of the branches */
141251881Speter    {
142251881Speter      g[0][3] = apr_psprintf(pool, "%-8ld", right_rev);
143251881Speter      g[1][3] = "|       ";
144251881Speter      g[2][3] = "-       ";
145251881Speter      g[3][3] = "        ";
146251881Speter      g[4][3] = "        ";
147251881Speter      g[5][3] = "-       ";
148251881Speter      g[6][3] = "|       ";
149251881Speter      g[7][3] = target_is_wc ? "WC      "
150251881Speter                             : apr_psprintf(pool, "%-8ld", target_rev);
151251881Speter    }
152251881Speter
153251881Speter  /* Find the width of each column, so we know how to print blank cells */
154251881Speter  for (col = 0; col < COLS; col++)
155251881Speter    {
156251881Speter      col_width[col] = 0;
157251881Speter      for (row = 0; row < ROWS; row++)
158251881Speter        {
159251881Speter          if (g[row][col] && ((int)strlen(g[row][col]) > col_width[col]))
160251881Speter            col_width[col] = (int)strlen(g[row][col]);
161251881Speter        }
162251881Speter    }
163251881Speter
164251881Speter  /* Column headings */
165251881Speter  SVN_ERR(svn_cmdline_printf(pool,
166251881Speter            "    %s\n"
167251881Speter            "    |         %s\n"
168251881Speter            "    |         |        %s\n"
169251881Speter            "    |         |        |         %s\n"
170251881Speter            "\n",
171251881Speter            _("youngest common ancestor"), _("last full merge"),
172251881Speter            _("tip of branch"), _("repository path")));
173251881Speter
174251881Speter  /* Print the diagram, row by row */
175251881Speter  for (row = 0; row < ROWS; row++)
176251881Speter    {
177251881Speter      SVN_ERR(svn_cmdline_fputs("  ", stdout, pool));
178251881Speter      for (col = 0; col < COLS; col++)
179251881Speter        {
180251881Speter          if (g[row][col])
181251881Speter            {
182251881Speter              SVN_ERR(svn_cmdline_fputs(g[row][col], stdout, pool));
183251881Speter            }
184251881Speter          else
185251881Speter            {
186251881Speter              /* Print <column-width> spaces */
187251881Speter              SVN_ERR(svn_cmdline_printf(pool, "%*s", col_width[col], ""));
188251881Speter            }
189251881Speter        }
190251881Speter      if (row == 2)
191251881Speter        SVN_ERR(svn_cmdline_printf(pool, "  %s",
192251881Speter                svn_uri_skip_ancestor(repos_root_url, right_url, pool)));
193251881Speter      if (row == 5)
194251881Speter        SVN_ERR(svn_cmdline_printf(pool, "  %s",
195251881Speter                svn_uri_skip_ancestor(repos_root_url, target_url, pool)));
196251881Speter      SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
197251881Speter    }
198251881Speter
199251881Speter  return SVN_NO_ERROR;
200251881Speter}
201251881Speter
202251881Speter/* Display a summary of the state of merging between the two branches
203251881Speter * SOURCE_PATH_OR_URL@SOURCE_REVISION and
204251881Speter * TARGET_PATH_OR_URL@TARGET_REVISION. */
205251881Speterstatic svn_error_t *
206251881Spetermergeinfo_summary(
207251881Speter                  const char *source_path_or_url,
208251881Speter                  const svn_opt_revision_t *source_revision,
209251881Speter                  const char *target_path_or_url,
210251881Speter                  const svn_opt_revision_t *target_revision,
211251881Speter                  svn_client_ctx_t *ctx,
212251881Speter                  apr_pool_t *pool)
213251881Speter{
214251881Speter  const char *yca_url, *base_url, *right_url, *target_url;
215251881Speter  svn_revnum_t yca_rev, base_rev, right_rev, target_rev;
216251881Speter  const char *repos_root_url;
217251881Speter  svn_boolean_t target_is_wc, is_reintegration;
218251881Speter
219251881Speter  target_is_wc = (! svn_path_is_url(target_path_or_url))
220251881Speter                 && (target_revision->kind == svn_opt_revision_unspecified
221251881Speter                     || target_revision->kind == svn_opt_revision_working);
222251881Speter  SVN_ERR(svn_client_get_merging_summary(
223251881Speter            &is_reintegration,
224251881Speter            &yca_url, &yca_rev,
225251881Speter            &base_url, &base_rev,
226251881Speter            &right_url, &right_rev,
227251881Speter            &target_url, &target_rev,
228251881Speter            &repos_root_url,
229251881Speter            source_path_or_url, source_revision,
230251881Speter            target_path_or_url, target_revision,
231251881Speter            ctx, pool, pool));
232251881Speter
233251881Speter  SVN_ERR(mergeinfo_diagram(yca_url, base_url, right_url, target_url,
234251881Speter                            yca_rev, base_rev, right_rev, target_rev,
235251881Speter                            repos_root_url, target_is_wc, is_reintegration,
236251881Speter                            pool));
237251881Speter
238251881Speter  return SVN_NO_ERROR;
239251881Speter}
240251881Speter
241251881Speter/* This implements the `svn_opt_subcommand_t' interface. */
242251881Spetersvn_error_t *
243251881Spetersvn_cl__mergeinfo(apr_getopt_t *os,
244251881Speter                  void *baton,
245251881Speter                  apr_pool_t *pool)
246251881Speter{
247251881Speter  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
248251881Speter  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
249251881Speter  apr_array_header_t *targets;
250251881Speter  const char *source, *target;
251251881Speter  svn_opt_revision_t src_peg_revision, tgt_peg_revision;
252251881Speter  svn_opt_revision_t *src_start_revision, *src_end_revision;
253251881Speter  /* Default to depth empty. */
254251881Speter  svn_depth_t depth = (opt_state->depth == svn_depth_unknown)
255251881Speter                      ? svn_depth_empty : opt_state->depth;
256251881Speter
257251881Speter  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
258251881Speter                                                      opt_state->targets,
259251881Speter                                                      ctx, FALSE, pool));
260251881Speter
261251881Speter  /* Parse the arguments: SOURCE[@REV] optionally followed by TARGET[@REV]. */
262251881Speter  if (targets->nelts < 1)
263251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
264251881Speter                            _("Not enough arguments given"));
265251881Speter  if (targets->nelts > 2)
266251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
267251881Speter                            _("Too many arguments given"));
268251881Speter  SVN_ERR(svn_opt_parse_path(&src_peg_revision, &source,
269251881Speter                             APR_ARRAY_IDX(targets, 0, const char *), pool));
270251881Speter  if (targets->nelts == 2)
271251881Speter    {
272251881Speter      SVN_ERR(svn_opt_parse_path(&tgt_peg_revision, &target,
273251881Speter                                 APR_ARRAY_IDX(targets, 1, const char *),
274251881Speter                                 pool));
275251881Speter    }
276251881Speter  else
277251881Speter    {
278251881Speter      target = "";
279251881Speter      tgt_peg_revision.kind = svn_opt_revision_unspecified;
280251881Speter    }
281251881Speter
282251881Speter  /* If no peg-rev was attached to the source URL, assume HEAD. */
283251881Speter  /* ### But what if SOURCE is a WC path not a URL -- shouldn't we then use
284251881Speter   *     BASE (but not WORKING: that would be inconsistent with 'svn merge')? */
285251881Speter  if (src_peg_revision.kind == svn_opt_revision_unspecified)
286251881Speter    src_peg_revision.kind = svn_opt_revision_head;
287251881Speter
288251881Speter  /* If no peg-rev was attached to a URL target, then assume HEAD; if
289251881Speter     no peg-rev was attached to a non-URL target, then assume BASE. */
290251881Speter  /* ### But we would like to be able to examine a working copy with an
291251881Speter         uncommitted merge in it, so change this to use WORKING not BASE? */
292251881Speter  if (tgt_peg_revision.kind == svn_opt_revision_unspecified)
293251881Speter    {
294251881Speter      if (svn_path_is_url(target))
295251881Speter        tgt_peg_revision.kind = svn_opt_revision_head;
296251881Speter      else
297251881Speter        tgt_peg_revision.kind = svn_opt_revision_base;
298251881Speter    }
299251881Speter
300251881Speter  src_start_revision = &(opt_state->start_revision);
301251881Speter  if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
302251881Speter    src_end_revision = src_start_revision;
303251881Speter  else
304251881Speter    src_end_revision = &(opt_state->end_revision);
305251881Speter
306251881Speter  /* Do the real work, depending on the requested data flavor. */
307251881Speter  if (opt_state->show_revs == svn_cl__show_revs_merged)
308251881Speter    {
309253734Speter      apr_array_header_t *revprops;
310253734Speter
311253734Speter      /* We need only revisions number, not revision properties. */
312253734Speter      revprops = apr_array_make(pool, 0, sizeof(const char *));
313253734Speter
314251881Speter      SVN_ERR(svn_client_mergeinfo_log2(TRUE, target, &tgt_peg_revision,
315251881Speter                                        source, &src_peg_revision,
316251881Speter                                        src_start_revision,
317251881Speter                                        src_end_revision,
318251881Speter                                        print_log_rev, NULL,
319253734Speter                                        TRUE, depth, revprops, ctx,
320251881Speter                                        pool));
321251881Speter    }
322251881Speter  else if (opt_state->show_revs == svn_cl__show_revs_eligible)
323251881Speter    {
324253734Speter      apr_array_header_t *revprops;
325253734Speter
326253734Speter      /* We need only revisions number, not revision properties. */
327253734Speter      revprops = apr_array_make(pool, 0, sizeof(const char *));
328253734Speter
329251881Speter      SVN_ERR(svn_client_mergeinfo_log2(FALSE, target, &tgt_peg_revision,
330251881Speter                                        source, &src_peg_revision,
331251881Speter                                        src_start_revision,
332251881Speter                                        src_end_revision,
333251881Speter                                        print_log_rev, NULL,
334253734Speter                                        TRUE, depth, revprops, ctx,
335251881Speter                                        pool));
336251881Speter    }
337251881Speter  else
338251881Speter    {
339251881Speter      if ((opt_state->start_revision.kind != svn_opt_revision_unspecified)
340251881Speter          || (opt_state->end_revision.kind != svn_opt_revision_unspecified))
341251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
342251881Speter                                _("--revision (-r) option valid only with "
343251881Speter                                  "--show-revs option"));
344251881Speter      if (opt_state->depth != svn_depth_unknown)
345251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
346251881Speter                                _("Depth specification options valid only "
347251881Speter                                  "with --show-revs option"));
348251881Speter
349251881Speter      SVN_ERR(mergeinfo_summary(source, &src_peg_revision,
350251881Speter                                target, &tgt_peg_revision,
351251881Speter                                ctx, pool));
352251881Speter    }
353251881Speter  return SVN_NO_ERROR;
354251881Speter}
355