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
80/* A callback of type svn_client_info_receiver2_t.
81   Prints svn info in xml mode to standard out */
82static svn_error_t *
83print_info_xml(void *baton,
84               const char *target,
85               const svn_client_info2_t *info,
86               apr_pool_t *pool)
87{
88  svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
89  const char *rev_str;
90  const char *path_prefix = baton;
91
92  if (SVN_IS_VALID_REVNUM(info->rev))
93    rev_str = apr_psprintf(pool, "%ld", info->rev);
94  else
95    rev_str = apr_pstrdup(pool, _("Resource is not under version control."));
96
97  /* "<entry ...>" */
98  svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
99                        "path", svn_cl__local_style_skip_ancestor(
100                                  path_prefix, target, pool),
101                        "kind", svn_cl__node_kind_str_xml(info->kind),
102                        "revision", rev_str,
103                        NULL);
104
105  /* "<url> xx </url>" */
106  svn_cl__xml_tagged_cdata(&sb, pool, "url", info->URL);
107
108  if (info->repos_root_URL && info->URL)
109    {
110      /* "<relative-url> xx </relative-url>" */
111      svn_cl__xml_tagged_cdata(&sb, pool, "relative-url",
112                               apr_pstrcat(pool, "^/",
113                                           svn_path_uri_encode(
114                                               svn_uri_skip_ancestor(
115                                                   info->repos_root_URL,
116                                                   info->URL, pool),
117                                               pool),
118                                           NULL));
119    }
120
121  if (info->repos_root_URL || info->repos_UUID)
122    {
123      /* "<repository>" */
124      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repository", NULL);
125
126      /* "<root> xx </root>" */
127      svn_cl__xml_tagged_cdata(&sb, pool, "root", info->repos_root_URL);
128
129      /* "<uuid> xx </uuid>" */
130      svn_cl__xml_tagged_cdata(&sb, pool, "uuid", info->repos_UUID);
131
132      /* "</repository>" */
133      svn_xml_make_close_tag(&sb, pool, "repository");
134    }
135
136  if (info->wc_info)
137    {
138      /* "<wc-info>" */
139      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "wc-info", NULL);
140
141      /* "<wcroot-abspath> xx </wcroot-abspath>" */
142      if (info->wc_info->wcroot_abspath)
143        svn_cl__xml_tagged_cdata(&sb, pool, "wcroot-abspath",
144                                 info->wc_info->wcroot_abspath);
145
146      /* "<schedule> xx </schedule>" */
147      svn_cl__xml_tagged_cdata(&sb, pool, "schedule",
148                               schedule_str(info->wc_info->schedule));
149
150      /* "<depth> xx </depth>" */
151      {
152        svn_depth_t depth = info->wc_info->depth;
153
154        /* In the entries world info just passed depth infinity for files */
155        if (depth == svn_depth_unknown && info->kind == svn_node_file)
156          depth = svn_depth_infinity;
157
158        svn_cl__xml_tagged_cdata(&sb, pool, "depth", svn_depth_to_word(depth));
159      }
160
161      /* "<copy-from-url> xx </copy-from-url>" */
162      svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-url",
163                               info->wc_info->copyfrom_url);
164
165      /* "<copy-from-rev> xx </copy-from-rev>" */
166      if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
167        svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-rev",
168                                 apr_psprintf(pool, "%ld",
169                                              info->wc_info->copyfrom_rev));
170
171      /* "<text-updated> xx </text-updated>" */
172      if (info->wc_info->recorded_time)
173        svn_cl__xml_tagged_cdata(&sb, pool, "text-updated",
174                                 svn_time_to_cstring(
175                                          info->wc_info->recorded_time,
176                                          pool));
177
178      /* "<checksum> xx </checksum>" */
179      /* ### Print the checksum kind. */
180      svn_cl__xml_tagged_cdata(&sb, pool, "checksum",
181                               svn_checksum_to_cstring(info->wc_info->checksum,
182                                                       pool));
183
184      if (info->wc_info->changelist)
185        /* "<changelist> xx </changelist>" */
186        svn_cl__xml_tagged_cdata(&sb, pool, "changelist",
187                                 info->wc_info->changelist);
188
189      if (info->wc_info->moved_from_abspath)
190        {
191          const char *relpath;
192
193          relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
194                                             info->wc_info->moved_from_abspath);
195
196          /* <moved-from> xx </moved-from> */
197          if (relpath && relpath[0] != '\0')
198            svn_cl__xml_tagged_cdata(&sb, pool, "moved-from", relpath);
199          else
200            svn_cl__xml_tagged_cdata(&sb, pool, "moved-from",
201                                     info->wc_info->moved_from_abspath);
202        }
203
204      if (info->wc_info->moved_to_abspath)
205        {
206          const char *relpath;
207
208          relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
209                                             info->wc_info->moved_to_abspath);
210          /* <moved-to> xx </moved-to> */
211          if (relpath && relpath[0] != '\0')
212            svn_cl__xml_tagged_cdata(&sb, pool, "moved-to", relpath);
213          else
214            svn_cl__xml_tagged_cdata(&sb, pool, "moved-to",
215                                     info->wc_info->moved_to_abspath);
216        }
217
218      /* "</wc-info>" */
219      svn_xml_make_close_tag(&sb, pool, "wc-info");
220    }
221
222  if (info->last_changed_author
223      || SVN_IS_VALID_REVNUM(info->last_changed_rev)
224      || info->last_changed_date)
225    {
226      svn_cl__print_xml_commit(&sb, info->last_changed_rev,
227                               info->last_changed_author,
228                               svn_time_to_cstring(info->last_changed_date,
229                                                   pool),
230                               pool);
231    }
232
233  if (info->wc_info && info->wc_info->conflicts)
234    {
235      int i;
236
237      for (i = 0; i < info->wc_info->conflicts->nelts; i++)
238        {
239          const svn_wc_conflict_description2_t *conflict =
240                      APR_ARRAY_IDX(info->wc_info->conflicts, i,
241                                    const svn_wc_conflict_description2_t *);
242
243          SVN_ERR(svn_cl__append_conflict_info_xml(sb, conflict, pool));
244        }
245    }
246
247  if (info->lock)
248    svn_cl__print_xml_lock(&sb, info->lock, pool);
249
250  /* "</entry>" */
251  svn_xml_make_close_tag(&sb, pool, "entry");
252
253  return svn_cl__error_checked_fputs(sb->data, stdout);
254}
255
256
257/* A callback of type svn_client_info_receiver2_t. */
258static svn_error_t *
259print_info(void *baton,
260           const char *target,
261           const svn_client_info2_t *info,
262           apr_pool_t *pool)
263{
264  const char *path_prefix = baton;
265
266  SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
267                             svn_cl__local_style_skip_ancestor(
268                               path_prefix, target, pool)));
269
270  /* ### remove this someday:  it's only here for cmdline output
271     compatibility with svn 1.1 and older.  */
272  if (info->kind != svn_node_dir)
273    SVN_ERR(svn_cmdline_printf(pool, _("Name: %s\n"),
274                               svn_dirent_basename(target, pool)));
275
276  if (info->wc_info && info->wc_info->wcroot_abspath)
277    SVN_ERR(svn_cmdline_printf(pool, _("Working Copy Root Path: %s\n"),
278                               svn_dirent_local_style(
279                                            info->wc_info->wcroot_abspath,
280                                            pool)));
281
282  if (info->URL)
283    SVN_ERR(svn_cmdline_printf(pool, _("URL: %s\n"), info->URL));
284
285  if (info->URL && info->repos_root_URL)
286    SVN_ERR(svn_cmdline_printf(pool, _("Relative URL: ^/%s\n"),
287                               svn_path_uri_encode(
288                                   svn_uri_skip_ancestor(info->repos_root_URL,
289                                                         info->URL, pool),
290                                   pool)));
291
292  if (info->repos_root_URL)
293    SVN_ERR(svn_cmdline_printf(pool, _("Repository Root: %s\n"),
294                               info->repos_root_URL));
295
296  if (info->repos_UUID)
297    SVN_ERR(svn_cmdline_printf(pool, _("Repository UUID: %s\n"),
298                               info->repos_UUID));
299
300  if (SVN_IS_VALID_REVNUM(info->rev))
301    SVN_ERR(svn_cmdline_printf(pool, _("Revision: %ld\n"), info->rev));
302
303  switch (info->kind)
304    {
305    case svn_node_file:
306      SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: file\n")));
307      break;
308
309    case svn_node_dir:
310      SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: directory\n")));
311      break;
312
313    case svn_node_none:
314      SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: none\n")));
315      break;
316
317    case svn_node_unknown:
318    default:
319      SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: unknown\n")));
320      break;
321    }
322
323  if (info->wc_info)
324    {
325      switch (info->wc_info->schedule)
326        {
327        case svn_wc_schedule_normal:
328          SVN_ERR(svn_cmdline_printf(pool, _("Schedule: normal\n")));
329          break;
330
331        case svn_wc_schedule_add:
332          SVN_ERR(svn_cmdline_printf(pool, _("Schedule: add\n")));
333          break;
334
335        case svn_wc_schedule_delete:
336          SVN_ERR(svn_cmdline_printf(pool, _("Schedule: delete\n")));
337          break;
338
339        case svn_wc_schedule_replace:
340          SVN_ERR(svn_cmdline_printf(pool, _("Schedule: replace\n")));
341          break;
342
343        default:
344          break;
345        }
346
347      switch (info->wc_info->depth)
348        {
349        case svn_depth_unknown:
350          /* Unknown depth is the norm for remote directories anyway
351             (although infinity would be equally appropriate).  Let's
352             not bother to print it. */
353          break;
354
355        case svn_depth_empty:
356          SVN_ERR(svn_cmdline_printf(pool, _("Depth: empty\n")));
357          break;
358
359        case svn_depth_files:
360          SVN_ERR(svn_cmdline_printf(pool, _("Depth: files\n")));
361          break;
362
363        case svn_depth_immediates:
364          SVN_ERR(svn_cmdline_printf(pool, _("Depth: immediates\n")));
365          break;
366
367        case svn_depth_exclude:
368          SVN_ERR(svn_cmdline_printf(pool, _("Depth: exclude\n")));
369          break;
370
371        case svn_depth_infinity:
372          /* Infinity is the default depth for working copy
373             directories.  Let's not print it, it's not special enough
374             to be worth mentioning.  */
375          break;
376
377        default:
378          /* Other depths should never happen here. */
379          SVN_ERR(svn_cmdline_printf(pool, _("Depth: INVALID\n")));
380        }
381
382      if (info->wc_info->copyfrom_url)
383        SVN_ERR(svn_cmdline_printf(pool, _("Copied From URL: %s\n"),
384                                   info->wc_info->copyfrom_url));
385
386      if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
387        SVN_ERR(svn_cmdline_printf(pool, _("Copied From Rev: %ld\n"),
388                                   info->wc_info->copyfrom_rev));
389      if (info->wc_info->moved_from_abspath)
390        {
391          const char *relpath;
392
393          relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
394                                             info->wc_info->moved_from_abspath);
395          if (relpath && relpath[0] != '\0')
396            SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"), relpath));
397          else
398            SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"),
399                                       info->wc_info->moved_from_abspath));
400        }
401
402      if (info->wc_info->moved_to_abspath)
403        {
404          const char *relpath;
405
406          relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
407                                             info->wc_info->moved_to_abspath);
408          if (relpath && relpath[0] != '\0')
409            SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"), relpath));
410          else
411            SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"),
412                                       info->wc_info->moved_to_abspath));
413        }
414    }
415
416  if (info->last_changed_author)
417    SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Author: %s\n"),
418                               info->last_changed_author));
419
420  if (SVN_IS_VALID_REVNUM(info->last_changed_rev))
421    SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Rev: %ld\n"),
422                               info->last_changed_rev));
423
424  if (info->last_changed_date)
425    SVN_ERR(svn_cl__info_print_time(info->last_changed_date,
426                                    _("Last Changed Date"), pool));
427
428  if (info->wc_info)
429    {
430      if (info->wc_info->recorded_time)
431        SVN_ERR(svn_cl__info_print_time(info->wc_info->recorded_time,
432                                        _("Text Last Updated"), pool));
433
434      if (info->wc_info->checksum)
435        SVN_ERR(svn_cmdline_printf(pool, _("Checksum: %s\n"),
436                                   svn_checksum_to_cstring(
437                                              info->wc_info->checksum, pool)));
438
439      if (info->wc_info->conflicts)
440        {
441          svn_boolean_t printed_prop_conflict_file = FALSE;
442          int i;
443
444          for (i = 0; i < info->wc_info->conflicts->nelts; i++)
445            {
446              const svn_wc_conflict_description2_t *conflict =
447                    APR_ARRAY_IDX(info->wc_info->conflicts, i,
448                                  const svn_wc_conflict_description2_t *);
449              const char *desc;
450
451              switch (conflict->kind)
452                {
453                  case svn_wc_conflict_kind_text:
454                    if (conflict->base_abspath)
455                      SVN_ERR(svn_cmdline_printf(pool,
456                                _("Conflict Previous Base File: %s\n"),
457                                svn_cl__local_style_skip_ancestor(
458                                        path_prefix, conflict->base_abspath,
459                                        pool)));
460
461                    if (conflict->my_abspath)
462                      SVN_ERR(svn_cmdline_printf(pool,
463                                _("Conflict Previous Working File: %s\n"),
464                                svn_cl__local_style_skip_ancestor(
465                                        path_prefix, conflict->my_abspath,
466                                        pool)));
467
468                    if (conflict->their_abspath)
469                      SVN_ERR(svn_cmdline_printf(pool,
470                                _("Conflict Current Base File: %s\n"),
471                                svn_cl__local_style_skip_ancestor(
472                                        path_prefix, conflict->their_abspath,
473                                        pool)));
474                  break;
475
476                  case svn_wc_conflict_kind_property:
477                    if (! printed_prop_conflict_file)
478                      SVN_ERR(svn_cmdline_printf(pool,
479                                _("Conflict Properties File: %s\n"),
480                                svn_dirent_local_style(conflict->their_abspath,
481                                                       pool)));
482                    printed_prop_conflict_file = TRUE;
483                  break;
484
485                  case svn_wc_conflict_kind_tree:
486                    SVN_ERR(
487                        svn_cl__get_human_readable_tree_conflict_description(
488                                                    &desc, conflict, pool));
489
490                    SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n",
491                                               _("Tree conflict"), desc));
492                  break;
493                }
494            }
495
496          /* We only store one left and right version for all conflicts, which is
497             referenced from all conflicts.
498             Print it after the conflicts to match the 1.6/1.7 output where it is
499             only available for tree conflicts */
500          {
501            const char *src_left_version;
502            const char *src_right_version;
503            const svn_wc_conflict_description2_t *conflict =
504                  APR_ARRAY_IDX(info->wc_info->conflicts, 0,
505                                const svn_wc_conflict_description2_t *);
506
507            src_left_version =
508                        svn_cl__node_description(conflict->src_left_version,
509                                                 info->repos_root_URL, pool);
510
511            src_right_version =
512                        svn_cl__node_description(conflict->src_right_version,
513                                                 info->repos_root_URL, pool);
514
515            if (src_left_version)
516              SVN_ERR(svn_cmdline_printf(pool, "  %s: %s\n",
517                                         _("Source  left"), /* (1) */
518                                         src_left_version));
519            /* (1): Sneaking in a space in "Source  left" so that
520             * it is the same length as "Source right" while it still
521             * starts in the same column. That's just a tiny tweak in
522             * the English `svn'. */
523
524            if (src_right_version)
525              SVN_ERR(svn_cmdline_printf(pool, "  %s: %s\n",
526                                         _("Source right"),
527                                         src_right_version));
528          }
529        }
530    }
531
532  if (info->lock)
533    {
534      if (info->lock->token)
535        SVN_ERR(svn_cmdline_printf(pool, _("Lock Token: %s\n"),
536                                   info->lock->token));
537
538      if (info->lock->owner)
539        SVN_ERR(svn_cmdline_printf(pool, _("Lock Owner: %s\n"),
540                                   info->lock->owner));
541
542      if (info->lock->creation_date)
543        SVN_ERR(svn_cl__info_print_time(info->lock->creation_date,
544                                        _("Lock Created"), pool));
545
546      if (info->lock->expiration_date)
547        SVN_ERR(svn_cl__info_print_time(info->lock->expiration_date,
548                                        _("Lock Expires"), pool));
549
550      if (info->lock->comment)
551        {
552          int comment_lines;
553          /* NOTE: The stdio will handle newline translation. */
554          comment_lines = svn_cstring_count_newlines(info->lock->comment) + 1;
555          SVN_ERR(svn_cmdline_printf(pool,
556                                     Q_("Lock Comment (%i line):\n%s\n",
557                                        "Lock Comment (%i lines):\n%s\n",
558                                        comment_lines),
559                                     comment_lines,
560                                     info->lock->comment));
561        }
562    }
563
564  if (info->wc_info && info->wc_info->changelist)
565    SVN_ERR(svn_cmdline_printf(pool, _("Changelist: %s\n"),
566                               info->wc_info->changelist));
567
568  /* Print extra newline separator. */
569  return svn_cmdline_printf(pool, "\n");
570}
571
572
573/* This implements the `svn_opt_subcommand_t' interface. */
574svn_error_t *
575svn_cl__info(apr_getopt_t *os,
576             void *baton,
577             apr_pool_t *pool)
578{
579  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
580  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
581  apr_array_header_t *targets = NULL;
582  apr_pool_t *subpool = svn_pool_create(pool);
583  int i;
584  svn_error_t *err;
585  svn_boolean_t seen_nonexistent_target = FALSE;
586  svn_opt_revision_t peg_revision;
587  svn_client_info_receiver2_t receiver;
588  const char *path_prefix;
589
590  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
591                                                      opt_state->targets,
592                                                      ctx, FALSE, pool));
593
594  /* Add "." if user passed 0 arguments. */
595  svn_opt_push_implicit_dot_target(targets, pool);
596
597  if (opt_state->xml)
598    {
599      receiver = print_info_xml;
600
601      /* If output is not incremental, output the XML header and wrap
602         everything in a top-level element. This makes the output in
603         its entirety a well-formed XML document. */
604      if (! opt_state->incremental)
605        SVN_ERR(svn_cl__xml_print_header("info", pool));
606    }
607  else
608    {
609      receiver = print_info;
610
611      if (opt_state->incremental)
612        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
613                                _("'incremental' option only valid in XML "
614                                  "mode"));
615    }
616
617  if (opt_state->depth == svn_depth_unknown)
618    opt_state->depth = svn_depth_empty;
619
620  SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", pool));
621
622  for (i = 0; i < targets->nelts; i++)
623    {
624      const char *truepath;
625      const char *target = APR_ARRAY_IDX(targets, i, const char *);
626
627      svn_pool_clear(subpool);
628      SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
629
630      /* Get peg revisions. */
631      SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool));
632
633      /* If no peg-rev was attached to a URL target, then assume HEAD. */
634      if (svn_path_is_url(truepath))
635        {
636          if (peg_revision.kind == svn_opt_revision_unspecified)
637            peg_revision.kind = svn_opt_revision_head;
638        }
639      else
640        {
641          SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool));
642        }
643
644      err = svn_client_info3(truepath,
645                             &peg_revision, &(opt_state->start_revision),
646                             opt_state->depth, TRUE, TRUE,
647                             opt_state->changelists,
648                             receiver, (void *) path_prefix,
649                             ctx, subpool);
650
651      if (err)
652        {
653          /* If one of the targets is a non-existent URL or wc-entry,
654             don't bail out.  Just warn and move on to the next target. */
655          if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
656              err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
657            {
658              svn_handle_warning2(stderr, err, "svn: ");
659              svn_error_clear(svn_cmdline_fprintf(stderr, subpool, "\n"));
660            }
661          else
662            {
663              return svn_error_trace(err);
664            }
665
666          svn_error_clear(err);
667          err = NULL;
668          seen_nonexistent_target = TRUE;
669        }
670    }
671  svn_pool_destroy(subpool);
672
673  if (opt_state->xml && (! opt_state->incremental))
674    SVN_ERR(svn_cl__xml_print_footer("info", pool));
675
676  if (seen_nonexistent_target)
677    return svn_error_create(
678      SVN_ERR_ILLEGAL_TARGET, NULL,
679      _("Could not display info for all targets because some "
680        "targets don't exist"));
681  else
682    return SVN_NO_ERROR;
683}
684