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