1251881Speter/*
2251881Speter * list-cmd.c -- list a URL
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter#include "svn_cmdline.h"
25251881Speter#include "svn_client.h"
26251881Speter#include "svn_error.h"
27251881Speter#include "svn_pools.h"
28251881Speter#include "svn_time.h"
29251881Speter#include "svn_xml.h"
30251881Speter#include "svn_dirent_uri.h"
31251881Speter#include "svn_path.h"
32251881Speter#include "svn_utf.h"
33251881Speter#include "svn_opt.h"
34251881Speter
35251881Speter#include "cl.h"
36251881Speter
37251881Speter#include "svn_private_config.h"
38251881Speter
39251881Speter
40251881Speter
41251881Speter/* Baton used when printing directory entries. */
42251881Speterstruct print_baton {
43251881Speter  svn_boolean_t verbose;
44251881Speter  svn_client_ctx_t *ctx;
45251881Speter
46251881Speter  /* To keep track of last seen external information. */
47251881Speter  const char *last_external_parent_url;
48251881Speter  const char *last_external_target;
49251881Speter  svn_boolean_t in_external;
50251881Speter};
51251881Speter
52289166Speter/* Field flags required for this function */
53289166Speterstatic const apr_uint32_t print_dirent_fields = SVN_DIRENT_KIND;
54289166Speterstatic const apr_uint32_t print_dirent_fields_verbose = (
55289166Speter    SVN_DIRENT_KIND  | SVN_DIRENT_SIZE | SVN_DIRENT_TIME |
56289166Speter    SVN_DIRENT_CREATED_REV | SVN_DIRENT_LAST_AUTHOR);
57289166Speter
58251881Speter/* This implements the svn_client_list_func2_t API, printing a single
59251881Speter   directory entry in text format. */
60251881Speterstatic svn_error_t *
61251881Speterprint_dirent(void *baton,
62251881Speter             const char *path,
63251881Speter             const svn_dirent_t *dirent,
64251881Speter             const svn_lock_t *lock,
65251881Speter             const char *abs_path,
66251881Speter             const char *external_parent_url,
67251881Speter             const char *external_target,
68251881Speter             apr_pool_t *scratch_pool)
69251881Speter{
70251881Speter  struct print_baton *pb = baton;
71251881Speter  const char *entryname;
72251881Speter  static const char *time_format_long = NULL;
73251881Speter  static const char *time_format_short = NULL;
74251881Speter
75251881Speter  SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) ||
76251881Speter                 (external_parent_url && external_target));
77251881Speter
78251881Speter  if (time_format_long == NULL)
79251881Speter    time_format_long = _("%b %d %H:%M");
80251881Speter  if (time_format_short == NULL)
81251881Speter    time_format_short = _("%b %d  %Y");
82251881Speter
83251881Speter  if (pb->ctx->cancel_func)
84251881Speter    SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
85251881Speter
86251881Speter  if (strcmp(path, "") == 0)
87251881Speter    {
88251881Speter      if (dirent->kind == svn_node_file)
89251881Speter        entryname = svn_dirent_basename(abs_path, scratch_pool);
90251881Speter      else if (pb->verbose)
91251881Speter        entryname = ".";
92251881Speter      else
93251881Speter        /* Don't bother to list if no useful information will be shown. */
94251881Speter        return SVN_NO_ERROR;
95251881Speter    }
96251881Speter  else
97251881Speter    entryname = path;
98251881Speter
99251881Speter  if (external_parent_url && external_target)
100251881Speter    {
101251881Speter      if ((pb->last_external_parent_url == NULL
102251881Speter           && pb->last_external_target == NULL)
103251881Speter          || (strcmp(pb->last_external_parent_url, external_parent_url) != 0
104251881Speter              || strcmp(pb->last_external_target, external_target) != 0))
105251881Speter        {
106251881Speter          SVN_ERR(svn_cmdline_printf(scratch_pool,
107251881Speter                                     _("Listing external '%s'"
108251881Speter                                       " defined on '%s':\n"),
109251881Speter                                     external_target,
110251881Speter                                     external_parent_url));
111251881Speter
112251881Speter          pb->last_external_parent_url = external_parent_url;
113251881Speter          pb->last_external_target = external_target;
114251881Speter        }
115251881Speter    }
116251881Speter
117251881Speter  if (pb->verbose)
118251881Speter    {
119251881Speter      apr_time_t now = apr_time_now();
120251881Speter      apr_time_exp_t exp_time;
121251881Speter      apr_status_t apr_err;
122251881Speter      apr_size_t size;
123251881Speter      char timestr[20];
124251881Speter      const char *sizestr, *utf8_timestr;
125251881Speter
126251881Speter      /* svn_time_to_human_cstring gives us something *way* too long
127251881Speter         to use for this, so we have to roll our own.  We include
128251881Speter         the year if the entry's time is not within half a year. */
129251881Speter      apr_time_exp_lt(&exp_time, dirent->time);
130251881Speter      if (apr_time_sec(now - dirent->time) < (365 * 86400 / 2)
131251881Speter          && apr_time_sec(dirent->time - now) < (365 * 86400 / 2))
132251881Speter        {
133251881Speter          apr_err = apr_strftime(timestr, &size, sizeof(timestr),
134251881Speter                                 time_format_long, &exp_time);
135251881Speter        }
136251881Speter      else
137251881Speter        {
138251881Speter          apr_err = apr_strftime(timestr, &size, sizeof(timestr),
139251881Speter                                 time_format_short, &exp_time);
140251881Speter        }
141251881Speter
142251881Speter      /* if that failed, just zero out the string and print nothing */
143251881Speter      if (apr_err)
144251881Speter        timestr[0] = '\0';
145251881Speter
146251881Speter      /* we need it in UTF-8. */
147251881Speter      SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, timestr, scratch_pool));
148251881Speter
149251881Speter      sizestr = apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT,
150251881Speter                             dirent->size);
151251881Speter
152251881Speter      return svn_cmdline_printf
153251881Speter              (scratch_pool, "%7ld %-8.8s %c %10s %12s %s%s\n",
154251881Speter               dirent->created_rev,
155251881Speter               dirent->last_author ? dirent->last_author : " ? ",
156251881Speter               lock ? 'O' : ' ',
157251881Speter               (dirent->kind == svn_node_file) ? sizestr : "",
158251881Speter               utf8_timestr,
159251881Speter               entryname,
160251881Speter               (dirent->kind == svn_node_dir) ? "/" : "");
161251881Speter    }
162251881Speter  else
163251881Speter    {
164251881Speter      return svn_cmdline_printf(scratch_pool, "%s%s\n", entryname,
165251881Speter                                (dirent->kind == svn_node_dir)
166251881Speter                                ? "/" : "");
167251881Speter    }
168251881Speter}
169251881Speter
170289166Speter/* Field flags required for this function */
171289166Speterstatic const apr_uint32_t print_dirent_xml_fields = (
172289166Speter    SVN_DIRENT_KIND  | SVN_DIRENT_SIZE | SVN_DIRENT_TIME |
173289166Speter    SVN_DIRENT_CREATED_REV | SVN_DIRENT_LAST_AUTHOR);
174251881Speter/* This implements the svn_client_list_func2_t API, printing a single dirent
175251881Speter   in XML format. */
176251881Speterstatic svn_error_t *
177251881Speterprint_dirent_xml(void *baton,
178251881Speter                 const char *path,
179251881Speter                 const svn_dirent_t *dirent,
180251881Speter                 const svn_lock_t *lock,
181251881Speter                 const char *abs_path,
182251881Speter                 const char *external_parent_url,
183251881Speter                 const char *external_target,
184251881Speter                 apr_pool_t *scratch_pool)
185251881Speter{
186251881Speter  struct print_baton *pb = baton;
187251881Speter  const char *entryname;
188251881Speter  svn_stringbuf_t *sb = svn_stringbuf_create_empty(scratch_pool);
189251881Speter
190251881Speter  SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) ||
191251881Speter                 (external_parent_url && external_target));
192251881Speter
193251881Speter  if (strcmp(path, "") == 0)
194251881Speter    {
195251881Speter      if (dirent->kind == svn_node_file)
196251881Speter        entryname = svn_dirent_basename(abs_path, scratch_pool);
197251881Speter      else
198251881Speter        /* Don't bother to list if no useful information will be shown. */
199251881Speter        return SVN_NO_ERROR;
200251881Speter    }
201251881Speter  else
202251881Speter    entryname = path;
203251881Speter
204251881Speter  if (pb->ctx->cancel_func)
205251881Speter    SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
206251881Speter
207251881Speter  if (external_parent_url && external_target)
208251881Speter    {
209251881Speter      if ((pb->last_external_parent_url == NULL
210251881Speter           && pb->last_external_target == NULL)
211251881Speter          || (strcmp(pb->last_external_parent_url, external_parent_url) != 0
212251881Speter              || strcmp(pb->last_external_target, external_target) != 0))
213251881Speter        {
214251881Speter          if (pb->in_external)
215251881Speter            {
216251881Speter              /* The external item being listed is different from the previous
217251881Speter                 one, so close the tag. */
218251881Speter              svn_xml_make_close_tag(&sb, scratch_pool, "external");
219251881Speter              pb->in_external = FALSE;
220251881Speter            }
221251881Speter
222251881Speter          svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "external",
223251881Speter                                "parent_url", external_parent_url,
224251881Speter                                "target", external_target,
225299742Sdim                                SVN_VA_NULL);
226251881Speter
227251881Speter          pb->last_external_parent_url = external_parent_url;
228251881Speter          pb->last_external_target = external_target;
229251881Speter          pb->in_external = TRUE;
230251881Speter        }
231251881Speter    }
232251881Speter
233251881Speter  svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "entry",
234251881Speter                        "kind", svn_cl__node_kind_str_xml(dirent->kind),
235299742Sdim                        SVN_VA_NULL);
236251881Speter
237251881Speter  svn_cl__xml_tagged_cdata(&sb, scratch_pool, "name", entryname);
238251881Speter
239251881Speter  if (dirent->kind == svn_node_file)
240251881Speter    {
241251881Speter      svn_cl__xml_tagged_cdata
242251881Speter        (&sb, scratch_pool, "size",
243251881Speter         apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, dirent->size));
244251881Speter    }
245251881Speter
246251881Speter  svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "commit",
247251881Speter                        "revision",
248251881Speter                        apr_psprintf(scratch_pool, "%ld", dirent->created_rev),
249299742Sdim                        SVN_VA_NULL);
250251881Speter  svn_cl__xml_tagged_cdata(&sb, scratch_pool, "author", dirent->last_author);
251251881Speter  if (dirent->time)
252251881Speter    svn_cl__xml_tagged_cdata(&sb, scratch_pool, "date",
253251881Speter                             svn_time_to_cstring(dirent->time, scratch_pool));
254251881Speter  svn_xml_make_close_tag(&sb, scratch_pool, "commit");
255251881Speter
256251881Speter  if (lock)
257251881Speter    {
258299742Sdim      svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "lock",
259299742Sdim                            SVN_VA_NULL);
260251881Speter      svn_cl__xml_tagged_cdata(&sb, scratch_pool, "token", lock->token);
261251881Speter      svn_cl__xml_tagged_cdata(&sb, scratch_pool, "owner", lock->owner);
262251881Speter      svn_cl__xml_tagged_cdata(&sb, scratch_pool, "comment", lock->comment);
263251881Speter      svn_cl__xml_tagged_cdata(&sb, scratch_pool, "created",
264251881Speter                               svn_time_to_cstring(lock->creation_date,
265251881Speter                                                   scratch_pool));
266251881Speter      if (lock->expiration_date != 0)
267251881Speter        svn_cl__xml_tagged_cdata(&sb, scratch_pool, "expires",
268251881Speter                                 svn_time_to_cstring
269251881Speter                                 (lock->expiration_date, scratch_pool));
270251881Speter      svn_xml_make_close_tag(&sb, scratch_pool, "lock");
271251881Speter    }
272251881Speter
273251881Speter  svn_xml_make_close_tag(&sb, scratch_pool, "entry");
274251881Speter
275251881Speter  return svn_cl__error_checked_fputs(sb->data, stdout);
276251881Speter}
277251881Speter
278251881Speter
279251881Speter/* This implements the `svn_opt_subcommand_t' interface. */
280251881Spetersvn_error_t *
281251881Spetersvn_cl__list(apr_getopt_t *os,
282251881Speter             void *baton,
283251881Speter             apr_pool_t *pool)
284251881Speter{
285251881Speter  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
286251881Speter  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
287251881Speter  apr_array_header_t *targets;
288251881Speter  int i;
289251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
290251881Speter  apr_uint32_t dirent_fields;
291251881Speter  struct print_baton pb;
292251881Speter  svn_boolean_t seen_nonexistent_target = FALSE;
293251881Speter  svn_error_t *err;
294251881Speter  svn_error_t *externals_err = SVN_NO_ERROR;
295251881Speter  struct svn_cl__check_externals_failed_notify_baton nwb;
296251881Speter
297251881Speter  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
298251881Speter                                                      opt_state->targets,
299251881Speter                                                      ctx, FALSE, pool));
300251881Speter
301251881Speter  /* Add "." if user passed 0 arguments */
302251881Speter  svn_opt_push_implicit_dot_target(targets, pool);
303251881Speter
304251881Speter  if (opt_state->xml)
305251881Speter    {
306251881Speter      /* The XML output contains all the information, so "--verbose"
307251881Speter         does not apply. */
308251881Speter      if (opt_state->verbose)
309251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
310251881Speter                                _("'verbose' option invalid in XML mode"));
311251881Speter
312251881Speter      /* If output is not incremental, output the XML header and wrap
313251881Speter         everything in a top-level element. This makes the output in
314251881Speter         its entirety a well-formed XML document. */
315251881Speter      if (! opt_state->incremental)
316251881Speter        SVN_ERR(svn_cl__xml_print_header("lists", pool));
317251881Speter    }
318251881Speter  else
319251881Speter    {
320251881Speter      if (opt_state->incremental)
321251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
322251881Speter                                _("'incremental' option only valid in XML "
323251881Speter                                  "mode"));
324251881Speter    }
325251881Speter
326289166Speter  if (opt_state->xml)
327289166Speter    dirent_fields = print_dirent_xml_fields;
328289166Speter  else if (opt_state->verbose)
329289166Speter    dirent_fields = print_dirent_fields_verbose;
330251881Speter  else
331289166Speter    dirent_fields = print_dirent_fields;
332251881Speter
333251881Speter  pb.ctx = ctx;
334251881Speter  pb.verbose = opt_state->verbose;
335251881Speter
336251881Speter  if (opt_state->depth == svn_depth_unknown)
337251881Speter    opt_state->depth = svn_depth_immediates;
338251881Speter
339251881Speter  if (opt_state->include_externals)
340251881Speter    {
341251881Speter      nwb.wrapped_func = ctx->notify_func2;
342251881Speter      nwb.wrapped_baton = ctx->notify_baton2;
343251881Speter      nwb.had_externals_error = FALSE;
344251881Speter      ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper;
345251881Speter      ctx->notify_baton2 = &nwb;
346251881Speter    }
347251881Speter
348251881Speter  /* For each target, try to list it. */
349251881Speter  for (i = 0; i < targets->nelts; i++)
350251881Speter    {
351251881Speter      const char *target = APR_ARRAY_IDX(targets, i, const char *);
352251881Speter      const char *truepath;
353251881Speter      svn_opt_revision_t peg_revision;
354251881Speter
355251881Speter      /* Initialize the following variables for
356251881Speter         every list target. */
357251881Speter      pb.last_external_parent_url = NULL;
358251881Speter      pb.last_external_target = NULL;
359251881Speter      pb.in_external = FALSE;
360251881Speter
361251881Speter      svn_pool_clear(subpool);
362251881Speter
363251881Speter      SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
364251881Speter
365251881Speter      /* Get peg revisions. */
366251881Speter      SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
367251881Speter                                 subpool));
368251881Speter
369251881Speter      if (opt_state->xml)
370251881Speter        {
371251881Speter          svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
372251881Speter          svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "list",
373251881Speter                                "path", truepath[0] == '\0' ? "." : truepath,
374299742Sdim                                SVN_VA_NULL);
375251881Speter          SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
376251881Speter        }
377251881Speter
378251881Speter      err = svn_client_list3(truepath, &peg_revision,
379251881Speter                             &(opt_state->start_revision),
380251881Speter                             opt_state->depth,
381251881Speter                             dirent_fields,
382251881Speter                             (opt_state->xml || opt_state->verbose),
383251881Speter                             opt_state->include_externals,
384251881Speter                             opt_state->xml ? print_dirent_xml : print_dirent,
385251881Speter                             &pb, ctx, subpool);
386251881Speter
387251881Speter      if (err)
388251881Speter        {
389251881Speter          /* If one of the targets is a non-existent URL or wc-entry,
390251881Speter             don't bail out.  Just warn and move on to the next target. */
391251881Speter          if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
392251881Speter              err->apr_err == SVN_ERR_FS_NOT_FOUND)
393251881Speter              svn_handle_warning2(stderr, err, "svn: ");
394251881Speter          else
395251881Speter              return svn_error_trace(err);
396251881Speter
397251881Speter          svn_error_clear(err);
398251881Speter          err = NULL;
399251881Speter          seen_nonexistent_target = TRUE;
400251881Speter        }
401251881Speter
402251881Speter      if (opt_state->xml)
403251881Speter        {
404251881Speter          svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
405251881Speter
406251881Speter          if (pb.in_external)
407251881Speter            {
408251881Speter              /* close the final external item's tag */
409251881Speter              svn_xml_make_close_tag(&sb, pool, "external");
410251881Speter              pb.in_external = FALSE;
411251881Speter            }
412251881Speter
413251881Speter          svn_xml_make_close_tag(&sb, pool, "list");
414251881Speter          SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
415251881Speter        }
416251881Speter    }
417251881Speter
418251881Speter  svn_pool_destroy(subpool);
419251881Speter
420251881Speter  if (opt_state->include_externals && nwb.had_externals_error)
421251881Speter    {
422251881Speter      externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS,
423251881Speter                                       NULL,
424251881Speter                                       _("Failure occurred processing one or "
425251881Speter                                         "more externals definitions"));
426251881Speter    }
427251881Speter
428251881Speter  if (opt_state->xml && ! opt_state->incremental)
429251881Speter    SVN_ERR(svn_cl__xml_print_footer("lists", pool));
430251881Speter
431251881Speter  if (seen_nonexistent_target)
432251881Speter    err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
433251881Speter          _("Could not list all targets because some targets don't exist"));
434299742Sdim  else
435299742Sdim    err = NULL;
436251881Speter
437251881Speter  return svn_error_compose_create(externals_err, err);
438251881Speter}
439