]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svn/info-cmd.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / svn / info-cmd.c
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 \f
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 \f
46 /*** Code. ***/
47
48 static svn_error_t *
49 svn_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 */
61 static const char *
62 schedule_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. */
81 static const char*
82 relative_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(). */
93 typedef 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. */
117 typedef 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 }
124 static 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
140 static 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. */
145 typedef 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  */
172 static svn_error_t *
173 find_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 */
244 static svn_error_t *
245 print_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. */
416 static svn_error_t *
417 print_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. */
738 static svn_error_t *
739 print_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. */
758 static svn_error_t *
759 print_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. */
776 static svn_error_t *
777 print_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. */
854 svn_error_t *
855 svn_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 }