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