info-cmd.c revision 299742
1/*
2 * info-cmd.c -- Display information about a resource
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_string.h"
31#include "svn_cmdline.h"
32#include "svn_wc.h"
33#include "svn_pools.h"
34#include "svn_error_codes.h"
35#include "svn_error.h"
36#include "svn_dirent_uri.h"
37#include "svn_path.h"
38#include "svn_time.h"
39#include "svn_xml.h"
40#include "cl.h"
41
42#include "svn_private_config.h"
43#include "cl-conflicts.h"
44
45
46/*** Code. ***/
47
48static svn_error_t *
49svn_cl__info_print_time(apr_time_t atime,
50                        const char *desc,
51                        apr_pool_t *pool)
52{
53  const char *time_utf8;
54
55  time_utf8 = svn_time_to_human_cstring(atime, pool);
56  return svn_cmdline_printf(pool, "%s: %s\n", desc, time_utf8);
57}
58
59
60/* Return string representation of SCHEDULE */
61static const char *
62schedule_str(svn_wc_schedule_t schedule)
63{
64  switch (schedule)
65    {
66    case svn_wc_schedule_normal:
67      return "normal";
68    case svn_wc_schedule_add:
69      return "add";
70    case svn_wc_schedule_delete:
71      return "delete";
72    case svn_wc_schedule_replace:
73      return "replace";
74    default:
75      return "none";
76    }
77}
78
79/* Return a relative URL from information in INFO using POOL for
80   temporary allocation. */
81static const char*
82relative_url(const svn_client_info2_t *info, apr_pool_t *pool)
83{
84  return apr_pstrcat(pool, "^/",
85                     svn_path_uri_encode(
86                         svn_uri_skip_ancestor(info->repos_root_URL,
87                                               info->URL, pool),
88                         pool), SVN_VA_NULL);
89}
90
91
92/* The kinds of items for print_info_item(). */
93typedef enum
94{
95  /* Entry kind */
96  info_item_kind,
97
98  /* Repository location. */
99  info_item_url,
100  info_item_relative_url,
101  info_item_repos_root_url,
102  info_item_repos_uuid,
103
104  /* Working copy revision or repository HEAD revision */
105  info_item_revision,
106
107  /* Commit details. */
108  info_item_last_changed_rev,
109  info_item_last_changed_date,
110  info_item_last_changed_author,
111
112  /* Working copy information */
113  info_item_wc_root
114} info_item_t;
115
116/* Mapping between option keywords and info_item_t. */
117typedef struct info_item_map_t
118{
119  const svn_string_t keyword;
120  const info_item_t print_what;
121} info_item_map_t;
122
123#define MAKE_STRING(x) { x, sizeof(x) - 1 }
124static const info_item_map_t info_item_map[] =
125  {
126    { MAKE_STRING("kind"),                info_item_kind },
127    { MAKE_STRING("url"),                 info_item_url },
128    { MAKE_STRING("relative-url"),        info_item_relative_url },
129    { MAKE_STRING("repos-root-url"),      info_item_repos_root_url },
130    { MAKE_STRING("repos-uuid"),          info_item_repos_uuid },
131    { MAKE_STRING("revision"),            info_item_revision },
132    { MAKE_STRING("last-changed-revision"),
133                                          info_item_last_changed_rev },
134    { MAKE_STRING("last-changed-date"),   info_item_last_changed_date },
135    { MAKE_STRING("last-changed-author"), info_item_last_changed_author },
136    { MAKE_STRING("wc-root"),             info_item_wc_root }
137  };
138#undef MAKE_STRING
139
140static const apr_size_t info_item_map_len =
141  (sizeof(info_item_map) / sizeof(info_item_map[0]));
142
143
144/* The baton type used by the info receiver functions. */
145typedef struct print_info_baton_t
146{
147  /* The path prefix that output paths should be normalized to. */
148  const char *path_prefix;
149
150  /*
151   * The following fields are used by print_info_item().
152   */
153
154  /* Which item to print. */
155  info_item_t print_what;
156
157  /* Do we expect to show info for multiple targets? */
158  svn_boolean_t multiple_targets;
159
160  /* TRUE iff the current is a local path. */
161  svn_boolean_t target_is_path;
162
163  /* Did we already print a line of output? */
164  svn_boolean_t start_new_line;
165} print_info_baton_t;
166
167
168/* Find the appropriate info_item_t for KEYWORD and initialize
169 * RECEIVER_BATON for print_info_item(). Use SCRATCH_POOL for
170 * temporary allocation.
171 */
172static svn_error_t *
173find_print_what(const char *keyword,
174                print_info_baton_t *receiver_baton,
175                apr_pool_t *scratch_pool)
176{
177  svn_cl__simcheck_t **keywords = apr_palloc(
178      scratch_pool, info_item_map_len * sizeof(svn_cl__simcheck_t*));
179  svn_cl__simcheck_t *kwbuf = apr_palloc(
180      scratch_pool, info_item_map_len * sizeof(svn_cl__simcheck_t));
181  apr_size_t i;
182
183  for (i = 0; i < info_item_map_len; ++i)
184    {
185      keywords[i] = &kwbuf[i];
186      kwbuf[i].token.data = info_item_map[i].keyword.data;
187      kwbuf[i].token.len = info_item_map[i].keyword.len;
188      kwbuf[i].data = &info_item_map[i];
189    }
190
191  switch (svn_cl__similarity_check(keyword, keywords,
192                                   info_item_map_len, scratch_pool))
193    {
194      const info_item_map_t *kw0;
195      const info_item_map_t *kw1;
196      const info_item_map_t *kw2;
197
198    case 0:                     /* Exact match. */
199      kw0 = keywords[0]->data;
200      receiver_baton->print_what = kw0->print_what;
201      return SVN_NO_ERROR;
202
203    case 1:
204      /* The best alternative isn't good enough */
205      return svn_error_createf(
206          SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
207          _("'%s' is not a valid value for --show-item"),
208          keyword);
209
210    case 2:
211      /* There is only one good candidate */
212      kw0 = keywords[0]->data;
213      return svn_error_createf(
214          SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
215          _("'%s' is not a valid value for --show-item;"
216            " did you mean '%s'?"),
217          keyword, kw0->keyword.data);
218
219    case 3:
220      /* Suggest a list of the most likely candidates */
221      kw0 = keywords[0]->data;
222      kw1 = keywords[1]->data;
223      return svn_error_createf(
224          SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
225          _("'%s' is not a valid value for --show-item;"
226            " did you mean '%s' or '%s'?"),
227          keyword, kw0->keyword.data, kw1->keyword.data);
228
229    default:
230      /* Never suggest more than three candidates */
231      kw0 = keywords[0]->data;
232      kw1 = keywords[1]->data;
233      kw2 = keywords[2]->data;
234      return svn_error_createf(
235          SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
236          _("'%s' is not a valid value for --show-item;"
237            " did you mean '%s', '%s' or '%s'?"),
238          keyword, kw0->keyword.data, kw1->keyword.data, kw2->keyword.data);
239    }
240}
241
242/* A callback of type svn_client_info_receiver2_t.
243   Prints svn info in xml mode to standard out */
244static svn_error_t *
245print_info_xml(void *baton,
246               const char *target,
247               const svn_client_info2_t *info,
248               apr_pool_t *pool)
249{
250  svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
251  const char *rev_str;
252  print_info_baton_t *const receiver_baton = baton;
253
254  if (SVN_IS_VALID_REVNUM(info->rev))
255    rev_str = apr_psprintf(pool, "%ld", info->rev);
256  else
257    rev_str = apr_pstrdup(pool, _("Resource is not under version control."));
258
259  /* "<entry ...>" */
260  svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
261                        "path", svn_cl__local_style_skip_ancestor(
262                                  receiver_baton->path_prefix, target, pool),
263                        "kind", svn_cl__node_kind_str_xml(info->kind),
264                        "revision", rev_str,
265                        SVN_VA_NULL);
266
267  /* "<url> xx </url>" */
268  svn_cl__xml_tagged_cdata(&sb, pool, "url", info->URL);
269
270  if (info->repos_root_URL && info->URL)
271    {
272      /* "<relative-url> xx </relative-url>" */
273      svn_cl__xml_tagged_cdata(&sb, pool, "relative-url",
274                               relative_url(info, pool));
275    }
276
277  if (info->repos_root_URL || info->repos_UUID)
278    {
279      /* "<repository>" */
280      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repository",
281                            SVN_VA_NULL);
282
283      /* "<root> xx </root>" */
284      svn_cl__xml_tagged_cdata(&sb, pool, "root", info->repos_root_URL);
285
286      /* "<uuid> xx </uuid>" */
287      svn_cl__xml_tagged_cdata(&sb, pool, "uuid", info->repos_UUID);
288
289      /* "</repository>" */
290      svn_xml_make_close_tag(&sb, pool, "repository");
291    }
292
293  if (info->wc_info)
294    {
295      /* "<wc-info>" */
296      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "wc-info",
297                            SVN_VA_NULL);
298
299      /* "<wcroot-abspath> xx </wcroot-abspath>" */
300      if (info->wc_info->wcroot_abspath)
301        svn_cl__xml_tagged_cdata(&sb, pool, "wcroot-abspath",
302                                 info->wc_info->wcroot_abspath);
303
304      /* "<schedule> xx </schedule>" */
305      svn_cl__xml_tagged_cdata(&sb, pool, "schedule",
306                               schedule_str(info->wc_info->schedule));
307
308      /* "<depth> xx </depth>" */
309      {
310        svn_depth_t depth = info->wc_info->depth;
311
312        /* In the entries world info just passed depth infinity for files */
313        if (depth == svn_depth_unknown && info->kind == svn_node_file)
314          depth = svn_depth_infinity;
315
316        svn_cl__xml_tagged_cdata(&sb, pool, "depth", svn_depth_to_word(depth));
317      }
318
319      /* "<copy-from-url> xx </copy-from-url>" */
320      svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-url",
321                               info->wc_info->copyfrom_url);
322
323      /* "<copy-from-rev> xx </copy-from-rev>" */
324      if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
325        svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-rev",
326                                 apr_psprintf(pool, "%ld",
327                                              info->wc_info->copyfrom_rev));
328
329      /* "<text-updated> xx </text-updated>" */
330      if (info->wc_info->recorded_time)
331        svn_cl__xml_tagged_cdata(&sb, pool, "text-updated",
332                                 svn_time_to_cstring(
333                                          info->wc_info->recorded_time,
334                                          pool));
335
336      /* "<checksum> xx </checksum>" */
337      /* ### Print the checksum kind. */
338      svn_cl__xml_tagged_cdata(&sb, pool, "checksum",
339                               svn_checksum_to_cstring(info->wc_info->checksum,
340                                                       pool));
341
342      if (info->wc_info->changelist)
343        /* "<changelist> xx </changelist>" */
344        svn_cl__xml_tagged_cdata(&sb, pool, "changelist",
345                                 info->wc_info->changelist);
346
347      if (info->wc_info->moved_from_abspath)
348        {
349          const char *relpath;
350
351          relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
352                                             info->wc_info->moved_from_abspath);
353
354          /* <moved-from> xx </moved-from> */
355          if (relpath && relpath[0] != '\0')
356            svn_cl__xml_tagged_cdata(&sb, pool, "moved-from", relpath);
357          else
358            svn_cl__xml_tagged_cdata(&sb, pool, "moved-from",
359                                     info->wc_info->moved_from_abspath);
360        }
361
362      if (info->wc_info->moved_to_abspath)
363        {
364          const char *relpath;
365
366          relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
367                                             info->wc_info->moved_to_abspath);
368          /* <moved-to> xx </moved-to> */
369          if (relpath && relpath[0] != '\0')
370            svn_cl__xml_tagged_cdata(&sb, pool, "moved-to", relpath);
371          else
372            svn_cl__xml_tagged_cdata(&sb, pool, "moved-to",
373                                     info->wc_info->moved_to_abspath);
374        }
375
376      /* "</wc-info>" */
377      svn_xml_make_close_tag(&sb, pool, "wc-info");
378    }
379
380  if (info->last_changed_author
381      || SVN_IS_VALID_REVNUM(info->last_changed_rev)
382      || info->last_changed_date)
383    {
384      svn_cl__print_xml_commit(&sb, info->last_changed_rev,
385                               info->last_changed_author,
386                               svn_time_to_cstring(info->last_changed_date,
387                                                   pool),
388                               pool);
389    }
390
391  if (info->wc_info && info->wc_info->conflicts)
392    {
393      int i;
394
395      for (i = 0; i < info->wc_info->conflicts->nelts; i++)
396        {
397          const svn_wc_conflict_description2_t *conflict =
398                      APR_ARRAY_IDX(info->wc_info->conflicts, i,
399                                    const svn_wc_conflict_description2_t *);
400
401          SVN_ERR(svn_cl__append_conflict_info_xml(sb, conflict, pool));
402        }
403    }
404
405  if (info->lock)
406    svn_cl__print_xml_lock(&sb, info->lock, pool);
407
408  /* "</entry>" */
409  svn_xml_make_close_tag(&sb, pool, "entry");
410
411  return svn_cl__error_checked_fputs(sb->data, stdout);
412}
413
414
415/* A callback of type svn_client_info_receiver2_t. */
416static svn_error_t *
417print_info(void *baton,
418           const char *target,
419           const svn_client_info2_t *info,
420           apr_pool_t *pool)
421{
422  print_info_baton_t *const receiver_baton = baton;
423
424  SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
425                             svn_cl__local_style_skip_ancestor(
426                               receiver_baton->path_prefix, target, pool)));
427
428  /* ### remove this someday:  it's only here for cmdline output
429     compatibility with svn 1.1 and older.  */
430  if (info->kind != svn_node_dir)
431    SVN_ERR(svn_cmdline_printf(pool, _("Name: %s\n"),
432                               svn_dirent_basename(target, pool)));
433
434  if (info->wc_info && info->wc_info->wcroot_abspath)
435    SVN_ERR(svn_cmdline_printf(pool, _("Working Copy Root Path: %s\n"),
436                               svn_dirent_local_style(
437                                            info->wc_info->wcroot_abspath,
438                                            pool)));
439
440  if (info->URL)
441    SVN_ERR(svn_cmdline_printf(pool, _("URL: %s\n"), info->URL));
442
443  if (info->URL && info->repos_root_URL)
444    SVN_ERR(svn_cmdline_printf(pool, _("Relative URL: %s\n"),
445                               relative_url(info, pool)));
446
447  if (info->repos_root_URL)
448    SVN_ERR(svn_cmdline_printf(pool, _("Repository Root: %s\n"),
449                               info->repos_root_URL));
450
451  if (info->repos_UUID)
452    SVN_ERR(svn_cmdline_printf(pool, _("Repository UUID: %s\n"),
453                               info->repos_UUID));
454
455  if (SVN_IS_VALID_REVNUM(info->rev))
456    SVN_ERR(svn_cmdline_printf(pool, _("Revision: %ld\n"), info->rev));
457
458  switch (info->kind)
459    {
460    case svn_node_file:
461      SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: file\n")));
462      break;
463
464    case svn_node_dir:
465      SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: directory\n")));
466      break;
467
468    case svn_node_none:
469      SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: none\n")));
470      break;
471
472    case svn_node_unknown:
473    default:
474      SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: unknown\n")));
475      break;
476    }
477
478  if (info->wc_info)
479    {
480      switch (info->wc_info->schedule)
481        {
482        case svn_wc_schedule_normal:
483          SVN_ERR(svn_cmdline_printf(pool, _("Schedule: normal\n")));
484          break;
485
486        case svn_wc_schedule_add:
487          SVN_ERR(svn_cmdline_printf(pool, _("Schedule: add\n")));
488          break;
489
490        case svn_wc_schedule_delete:
491          SVN_ERR(svn_cmdline_printf(pool, _("Schedule: delete\n")));
492          break;
493
494        case svn_wc_schedule_replace:
495          SVN_ERR(svn_cmdline_printf(pool, _("Schedule: replace\n")));
496          break;
497
498        default:
499          break;
500        }
501
502      switch (info->wc_info->depth)
503        {
504        case svn_depth_unknown:
505          /* Unknown depth is the norm for remote directories anyway
506             (although infinity would be equally appropriate).  Let's
507             not bother to print it. */
508          break;
509
510        case svn_depth_empty:
511          SVN_ERR(svn_cmdline_printf(pool, _("Depth: empty\n")));
512          break;
513
514        case svn_depth_files:
515          SVN_ERR(svn_cmdline_printf(pool, _("Depth: files\n")));
516          break;
517
518        case svn_depth_immediates:
519          SVN_ERR(svn_cmdline_printf(pool, _("Depth: immediates\n")));
520          break;
521
522        case svn_depth_exclude:
523          SVN_ERR(svn_cmdline_printf(pool, _("Depth: exclude\n")));
524          break;
525
526        case svn_depth_infinity:
527          /* Infinity is the default depth for working copy
528             directories.  Let's not print it, it's not special enough
529             to be worth mentioning.  */
530          break;
531
532        default:
533          /* Other depths should never happen here. */
534          SVN_ERR(svn_cmdline_printf(pool, _("Depth: INVALID\n")));
535        }
536
537      if (info->wc_info->copyfrom_url)
538        SVN_ERR(svn_cmdline_printf(pool, _("Copied From URL: %s\n"),
539                                   info->wc_info->copyfrom_url));
540
541      if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
542        SVN_ERR(svn_cmdline_printf(pool, _("Copied From Rev: %ld\n"),
543                                   info->wc_info->copyfrom_rev));
544      if (info->wc_info->moved_from_abspath)
545        SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"),
546                                   svn_cl__local_style_skip_ancestor(
547                                      receiver_baton->path_prefix,
548                                      info->wc_info->moved_from_abspath,
549                                      pool)));
550
551      if (info->wc_info->moved_to_abspath)
552        SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"),
553                                   svn_cl__local_style_skip_ancestor(
554                                      receiver_baton->path_prefix,
555                                      info->wc_info->moved_to_abspath,
556                                      pool)));
557    }
558
559  if (info->last_changed_author)
560    SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Author: %s\n"),
561                               info->last_changed_author));
562
563  if (SVN_IS_VALID_REVNUM(info->last_changed_rev))
564    SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Rev: %ld\n"),
565                               info->last_changed_rev));
566
567  if (info->last_changed_date)
568    SVN_ERR(svn_cl__info_print_time(info->last_changed_date,
569                                    _("Last Changed Date"), pool));
570
571  if (info->wc_info)
572    {
573      if (info->wc_info->recorded_time)
574        SVN_ERR(svn_cl__info_print_time(info->wc_info->recorded_time,
575                                        _("Text Last Updated"), pool));
576
577      if (info->wc_info->checksum)
578        SVN_ERR(svn_cmdline_printf(pool, _("Checksum: %s\n"),
579                                   svn_checksum_to_cstring(
580                                              info->wc_info->checksum, pool)));
581
582      if (info->wc_info->conflicts)
583        {
584          svn_boolean_t printed_prop_conflict_file = FALSE;
585          svn_boolean_t printed_tc = FALSE;
586          int i;
587
588          for (i = 0; i < info->wc_info->conflicts->nelts; i++)
589            {
590              const svn_wc_conflict_description2_t *conflict =
591                    APR_ARRAY_IDX(info->wc_info->conflicts, i,
592                                  const svn_wc_conflict_description2_t *);
593              const char *desc;
594
595              switch (conflict->kind)
596                {
597                  case svn_wc_conflict_kind_text:
598                    if (conflict->base_abspath)
599                      SVN_ERR(svn_cmdline_printf(pool,
600                                _("Conflict Previous Base File: %s\n"),
601                                svn_cl__local_style_skip_ancestor(
602                                        receiver_baton->path_prefix,
603                                        conflict->base_abspath,
604                                        pool)));
605
606                    if (conflict->my_abspath)
607                      SVN_ERR(svn_cmdline_printf(pool,
608                                _("Conflict Previous Working File: %s\n"),
609                                svn_cl__local_style_skip_ancestor(
610                                        receiver_baton->path_prefix,
611                                        conflict->my_abspath,
612                                        pool)));
613
614                    if (conflict->their_abspath)
615                      SVN_ERR(svn_cmdline_printf(pool,
616                                _("Conflict Current Base File: %s\n"),
617                                svn_cl__local_style_skip_ancestor(
618                                        receiver_baton->path_prefix,
619                                        conflict->their_abspath,
620                                        pool)));
621                  break;
622
623                  case svn_wc_conflict_kind_property:
624                    if (! printed_prop_conflict_file)
625                      SVN_ERR(svn_cmdline_printf(pool,
626                                _("Conflict Properties File: %s\n"),
627                                svn_cl__local_style_skip_ancestor(
628                                        receiver_baton->path_prefix,
629                                        conflict->prop_reject_abspath,
630                                        pool)));
631                    printed_prop_conflict_file = TRUE;
632                  break;
633
634                  case svn_wc_conflict_kind_tree:
635                    printed_tc = TRUE;
636                    SVN_ERR(
637                        svn_cl__get_human_readable_tree_conflict_description(
638                                                    &desc, conflict, pool));
639
640                    SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n",
641                                               _("Tree conflict"), desc));
642                  break;
643                }
644            }
645
646          /* We only store one left and right version for all conflicts, which is
647             referenced from all conflicts.
648             Print it after the conflicts to match the 1.6/1.7 output where it is
649             only available for tree conflicts */
650          {
651            const char *src_left_version;
652            const char *src_right_version;
653            const svn_wc_conflict_description2_t *conflict =
654                  APR_ARRAY_IDX(info->wc_info->conflicts, 0,
655                                const svn_wc_conflict_description2_t *);
656
657            if (!printed_tc)
658              {
659                const char *desc;
660
661                SVN_ERR(svn_cl__get_human_readable_action_description(&desc,
662                                        svn_wc_conflict_action_edit,
663                                        conflict->operation,
664                                        conflict->node_kind, pool));
665
666                SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n",
667                                               _("Conflict Details"), desc));
668              }
669
670            src_left_version =
671                        svn_cl__node_description(conflict->src_left_version,
672                                                 info->repos_root_URL, pool);
673
674            src_right_version =
675                        svn_cl__node_description(conflict->src_right_version,
676                                                 info->repos_root_URL, pool);
677
678            if (src_left_version)
679              SVN_ERR(svn_cmdline_printf(pool, "  %s: %s\n",
680                                         _("Source  left"), /* (1) */
681                                         src_left_version));
682            /* (1): Sneaking in a space in "Source  left" so that
683             * it is the same length as "Source right" while it still
684             * starts in the same column. That's just a tiny tweak in
685             * the English `svn'. */
686
687            if (src_right_version)
688              SVN_ERR(svn_cmdline_printf(pool, "  %s: %s\n",
689                                         _("Source right"),
690                                         src_right_version));
691          }
692        }
693    }
694
695  if (info->lock)
696    {
697      if (info->lock->token)
698        SVN_ERR(svn_cmdline_printf(pool, _("Lock Token: %s\n"),
699                                   info->lock->token));
700
701      if (info->lock->owner)
702        SVN_ERR(svn_cmdline_printf(pool, _("Lock Owner: %s\n"),
703                                   info->lock->owner));
704
705      if (info->lock->creation_date)
706        SVN_ERR(svn_cl__info_print_time(info->lock->creation_date,
707                                        _("Lock Created"), pool));
708
709      if (info->lock->expiration_date)
710        SVN_ERR(svn_cl__info_print_time(info->lock->expiration_date,
711                                        _("Lock Expires"), pool));
712
713      if (info->lock->comment)
714        {
715          int comment_lines;
716          /* NOTE: The stdio will handle newline translation. */
717          comment_lines = svn_cstring_count_newlines(info->lock->comment) + 1;
718          SVN_ERR(svn_cmdline_printf(pool,
719                                     Q_("Lock Comment (%i line):\n%s\n",
720                                        "Lock Comment (%i lines):\n%s\n",
721                                        comment_lines),
722                                     comment_lines,
723                                     info->lock->comment));
724        }
725    }
726
727  if (info->wc_info && info->wc_info->changelist)
728    SVN_ERR(svn_cmdline_printf(pool, _("Changelist: %s\n"),
729                               info->wc_info->changelist));
730
731  /* Print extra newline separator. */
732  return svn_cmdline_printf(pool, "\n");
733}
734
735
736/* Helper for print_info_item(): Print the value TEXT for TARGET_PATH,
737   either of which may be NULL. Use POOL for temporary allocation. */
738static svn_error_t *
739print_info_item_string(const char *text, const char *target_path,
740                       apr_pool_t *pool)
741{
742  if (text)
743    {
744      if (target_path)
745        SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", text, target_path));
746      else
747        SVN_ERR(svn_cmdline_fputs(text, stdout, pool));
748    }
749  else if (target_path)
750    SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", "", target_path));
751
752  return SVN_NO_ERROR;
753}
754
755/* Helper for print_info_item(): Print the revision number REV, which
756   may be SVN_INVALID_REVNUM, for TARGET_PATH, which may be NULL. Use
757   POOL for temporary allocation. */
758static svn_error_t *
759print_info_item_revision(svn_revnum_t rev, const char *target_path,
760                         apr_pool_t *pool)
761{
762  if (SVN_IS_VALID_REVNUM(rev))
763    {
764      if (target_path)
765        SVN_ERR(svn_cmdline_printf(pool, "%-10ld %s", rev, target_path));
766      else
767        SVN_ERR(svn_cmdline_printf(pool, "%-10ld", rev));
768    }
769  else if (target_path)
770    SVN_ERR(svn_cmdline_printf(pool, "%-10s %s", "", target_path));
771
772  return SVN_NO_ERROR;
773}
774
775/* A callback of type svn_client_info_receiver2_t. */
776static svn_error_t *
777print_info_item(void *baton,
778                  const char *target,
779                  const svn_client_info2_t *info,
780                  apr_pool_t *pool)
781{
782  print_info_baton_t *const receiver_baton = baton;
783  const char *const target_path =
784    (!receiver_baton->multiple_targets ? NULL
785     : (!receiver_baton->target_is_path ? info->URL
786        : svn_cl__local_style_skip_ancestor(
787            receiver_baton->path_prefix, target, pool)));
788
789  if (receiver_baton->start_new_line)
790    SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
791
792  switch (receiver_baton->print_what)
793    {
794    case info_item_kind:
795      SVN_ERR(print_info_item_string(svn_node_kind_to_word(info->kind),
796                                     target_path, pool));
797      break;
798
799    case info_item_url:
800      SVN_ERR(print_info_item_string(info->URL, target_path, pool));
801      break;
802
803    case info_item_relative_url:
804      SVN_ERR(print_info_item_string(relative_url(info, pool),
805                                     target_path, pool));
806      break;
807
808    case info_item_repos_root_url:
809      SVN_ERR(print_info_item_string(info->repos_root_URL, target_path, pool));
810      break;
811
812    case info_item_repos_uuid:
813      SVN_ERR(print_info_item_string(info->repos_UUID, target_path, pool));
814      break;
815
816    case info_item_revision:
817      SVN_ERR(print_info_item_revision(info->rev, target_path, pool));
818      break;
819
820    case info_item_last_changed_rev:
821      SVN_ERR(print_info_item_revision(info->last_changed_rev,
822                                       target_path, pool));
823      break;
824
825    case info_item_last_changed_date:
826      SVN_ERR(print_info_item_string(
827                  (!info->last_changed_date ? NULL
828                   : svn_time_to_cstring(info->last_changed_date, pool)),
829                  target_path, pool));
830      break;
831
832    case info_item_last_changed_author:
833      SVN_ERR(print_info_item_string(info->last_changed_author,
834                                     target_path, pool));
835      break;
836
837    case info_item_wc_root:
838      SVN_ERR(print_info_item_string(
839                  (info->wc_info && info->wc_info->wcroot_abspath
840                   ? info->wc_info->wcroot_abspath : NULL),
841                  target_path, pool));
842      break;
843
844    default:
845      SVN_ERR_MALFUNCTION();
846    }
847
848  receiver_baton->start_new_line = TRUE;
849  return SVN_NO_ERROR;
850}
851
852
853/* This implements the `svn_opt_subcommand_t' interface. */
854svn_error_t *
855svn_cl__info(apr_getopt_t *os,
856             void *baton,
857             apr_pool_t *pool)
858{
859  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
860  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
861  apr_array_header_t *targets = NULL;
862  apr_pool_t *subpool = svn_pool_create(pool);
863  int i;
864  svn_error_t *err;
865  svn_boolean_t seen_nonexistent_target = FALSE;
866  svn_opt_revision_t peg_revision;
867  svn_client_info_receiver2_t receiver;
868  print_info_baton_t receiver_baton = { 0 };
869
870  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
871                                                      opt_state->targets,
872                                                      ctx, FALSE, pool));
873
874  /* Add "." if user passed 0 arguments. */
875  svn_opt_push_implicit_dot_target(targets, pool);
876
877  if (opt_state->xml)
878    {
879      receiver = print_info_xml;
880
881      if (opt_state->show_item)
882        return svn_error_create(
883            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
884            _("--show-item is not valid in --xml mode"));
885      if (opt_state->no_newline)
886        return svn_error_create(
887            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
888            _("--no-newline is not valid in --xml mode"));
889
890      /* If output is not incremental, output the XML header and wrap
891         everything in a top-level element. This makes the output in
892         its entirety a well-formed XML document. */
893      if (! opt_state->incremental)
894        SVN_ERR(svn_cl__xml_print_header("info", pool));
895    }
896  else if (opt_state->show_item)
897    {
898      receiver = print_info_item;
899
900      if (opt_state->incremental)
901        return svn_error_create(
902            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
903            _("--incremental is only valid in --xml mode"));
904
905      receiver_baton.multiple_targets = (opt_state->depth > svn_depth_empty
906                                         || targets->nelts > 1);
907      if (receiver_baton.multiple_targets && opt_state->no_newline)
908        return svn_error_create(
909            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
910            _("--no-newline is only available for single-target,"
911              " non-recursive info operations"));
912
913      SVN_ERR(find_print_what(opt_state->show_item, &receiver_baton, pool));
914      receiver_baton.start_new_line = FALSE;
915    }
916  else
917    {
918      receiver = print_info;
919
920      if (opt_state->incremental)
921        return svn_error_create(
922            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
923            _("--incremental is only valid in --xml mode"));
924      if (opt_state->no_newline)
925        return svn_error_create(
926            SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
927            _("--no-newline' is only valid with --show-item"));
928    }
929
930  if (opt_state->depth == svn_depth_unknown)
931    opt_state->depth = svn_depth_empty;
932
933  SVN_ERR(svn_dirent_get_absolute(&receiver_baton.path_prefix, "", pool));
934
935  for (i = 0; i < targets->nelts; i++)
936    {
937      const char *truepath;
938      const char *target = APR_ARRAY_IDX(targets, i, const char *);
939
940      svn_pool_clear(subpool);
941      SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
942
943      /* Get peg revisions. */
944      SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool));
945
946      /* If no peg-rev was attached to a URL target, then assume HEAD. */
947      if (svn_path_is_url(truepath))
948        {
949          if (peg_revision.kind == svn_opt_revision_unspecified)
950            peg_revision.kind = svn_opt_revision_head;
951          receiver_baton.target_is_path = FALSE;
952        }
953      else
954        {
955          SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool));
956          receiver_baton.target_is_path = TRUE;
957        }
958
959      err = svn_client_info4(truepath,
960                             &peg_revision, &(opt_state->start_revision),
961                             opt_state->depth,
962                             TRUE /* fetch_excluded */,
963                             TRUE /* fetch_actual_only */,
964                             opt_state->include_externals,
965                             opt_state->changelists,
966                             receiver, &receiver_baton,
967                             ctx, subpool);
968
969      if (err)
970        {
971          /* If one of the targets is a non-existent URL or wc-entry,
972             don't bail out.  Just warn and move on to the next target. */
973          if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
974              err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
975            {
976              svn_handle_warning2(stderr, err, "svn: ");
977              svn_error_clear(svn_cmdline_fprintf(stderr, subpool, "\n"));
978            }
979          else
980            {
981              return svn_error_trace(err);
982            }
983
984          svn_error_clear(err);
985          err = NULL;
986          seen_nonexistent_target = TRUE;
987        }
988    }
989  svn_pool_destroy(subpool);
990
991  if (opt_state->xml && (! opt_state->incremental))
992    SVN_ERR(svn_cl__xml_print_footer("info", pool));
993  else if (opt_state->show_item && !opt_state->no_newline
994           && receiver_baton.start_new_line)
995    SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
996
997  if (seen_nonexistent_target)
998    return svn_error_create(
999      SVN_ERR_ILLEGAL_TARGET, NULL,
1000      _("Could not display info for all targets because some "
1001        "targets don't exist"));
1002  else
1003    return SVN_NO_ERROR;
1004}
1005