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