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
52251881Speter/* This implements the svn_client_list_func2_t API, printing a single
53251881Speter   directory entry in text format. */
54251881Speterstatic svn_error_t *
55251881Speterprint_dirent(void *baton,
56251881Speter             const char *path,
57251881Speter             const svn_dirent_t *dirent,
58251881Speter             const svn_lock_t *lock,
59251881Speter             const char *abs_path,
60251881Speter             const char *external_parent_url,
61251881Speter             const char *external_target,
62251881Speter             apr_pool_t *scratch_pool)
63251881Speter{
64251881Speter  struct print_baton *pb = baton;
65251881Speter  const char *entryname;
66251881Speter  static const char *time_format_long = NULL;
67251881Speter  static const char *time_format_short = NULL;
68251881Speter
69251881Speter  SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) ||
70251881Speter                 (external_parent_url && external_target));
71251881Speter
72251881Speter  if (time_format_long == NULL)
73251881Speter    time_format_long = _("%b %d %H:%M");
74251881Speter  if (time_format_short == NULL)
75251881Speter    time_format_short = _("%b %d  %Y");
76251881Speter
77251881Speter  if (pb->ctx->cancel_func)
78251881Speter    SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
79251881Speter
80251881Speter  if (strcmp(path, "") == 0)
81251881Speter    {
82251881Speter      if (dirent->kind == svn_node_file)
83251881Speter        entryname = svn_dirent_basename(abs_path, scratch_pool);
84251881Speter      else if (pb->verbose)
85251881Speter        entryname = ".";
86251881Speter      else
87251881Speter        /* Don't bother to list if no useful information will be shown. */
88251881Speter        return SVN_NO_ERROR;
89251881Speter    }
90251881Speter  else
91251881Speter    entryname = path;
92251881Speter
93251881Speter  if (external_parent_url && external_target)
94251881Speter    {
95251881Speter      if ((pb->last_external_parent_url == NULL
96251881Speter           && pb->last_external_target == NULL)
97251881Speter          || (strcmp(pb->last_external_parent_url, external_parent_url) != 0
98251881Speter              || strcmp(pb->last_external_target, external_target) != 0))
99251881Speter        {
100251881Speter          SVN_ERR(svn_cmdline_printf(scratch_pool,
101251881Speter                                     _("Listing external '%s'"
102251881Speter                                       " defined on '%s':\n"),
103251881Speter                                     external_target,
104251881Speter                                     external_parent_url));
105251881Speter
106251881Speter          pb->last_external_parent_url = external_parent_url;
107251881Speter          pb->last_external_target = external_target;
108251881Speter        }
109251881Speter    }
110251881Speter
111251881Speter  if (pb->verbose)
112251881Speter    {
113251881Speter      apr_time_t now = apr_time_now();
114251881Speter      apr_time_exp_t exp_time;
115251881Speter      apr_status_t apr_err;
116251881Speter      apr_size_t size;
117251881Speter      char timestr[20];
118251881Speter      const char *sizestr, *utf8_timestr;
119251881Speter
120251881Speter      /* svn_time_to_human_cstring gives us something *way* too long
121251881Speter         to use for this, so we have to roll our own.  We include
122251881Speter         the year if the entry's time is not within half a year. */
123251881Speter      apr_time_exp_lt(&exp_time, dirent->time);
124251881Speter      if (apr_time_sec(now - dirent->time) < (365 * 86400 / 2)
125251881Speter          && apr_time_sec(dirent->time - now) < (365 * 86400 / 2))
126251881Speter        {
127251881Speter          apr_err = apr_strftime(timestr, &size, sizeof(timestr),
128251881Speter                                 time_format_long, &exp_time);
129251881Speter        }
130251881Speter      else
131251881Speter        {
132251881Speter          apr_err = apr_strftime(timestr, &size, sizeof(timestr),
133251881Speter                                 time_format_short, &exp_time);
134251881Speter        }
135251881Speter
136251881Speter      /* if that failed, just zero out the string and print nothing */
137251881Speter      if (apr_err)
138251881Speter        timestr[0] = '\0';
139251881Speter
140251881Speter      /* we need it in UTF-8. */
141251881Speter      SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, timestr, scratch_pool));
142251881Speter
143251881Speter      sizestr = apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT,
144251881Speter                             dirent->size);
145251881Speter
146251881Speter      return svn_cmdline_printf
147251881Speter              (scratch_pool, "%7ld %-8.8s %c %10s %12s %s%s\n",
148251881Speter               dirent->created_rev,
149251881Speter               dirent->last_author ? dirent->last_author : " ? ",
150251881Speter               lock ? 'O' : ' ',
151251881Speter               (dirent->kind == svn_node_file) ? sizestr : "",
152251881Speter               utf8_timestr,
153251881Speter               entryname,
154251881Speter               (dirent->kind == svn_node_dir) ? "/" : "");
155251881Speter    }
156251881Speter  else
157251881Speter    {
158251881Speter      return svn_cmdline_printf(scratch_pool, "%s%s\n", entryname,
159251881Speter                                (dirent->kind == svn_node_dir)
160251881Speter                                ? "/" : "");
161251881Speter    }
162251881Speter}
163251881Speter
164251881Speter
165251881Speter/* This implements the svn_client_list_func2_t API, printing a single dirent
166251881Speter   in XML format. */
167251881Speterstatic svn_error_t *
168251881Speterprint_dirent_xml(void *baton,
169251881Speter                 const char *path,
170251881Speter                 const svn_dirent_t *dirent,
171251881Speter                 const svn_lock_t *lock,
172251881Speter                 const char *abs_path,
173251881Speter                 const char *external_parent_url,
174251881Speter                 const char *external_target,
175251881Speter                 apr_pool_t *scratch_pool)
176251881Speter{
177251881Speter  struct print_baton *pb = baton;
178251881Speter  const char *entryname;
179251881Speter  svn_stringbuf_t *sb = svn_stringbuf_create_empty(scratch_pool);
180251881Speter
181251881Speter  SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) ||
182251881Speter                 (external_parent_url && external_target));
183251881Speter
184251881Speter  if (strcmp(path, "") == 0)
185251881Speter    {
186251881Speter      if (dirent->kind == svn_node_file)
187251881Speter        entryname = svn_dirent_basename(abs_path, scratch_pool);
188251881Speter      else
189251881Speter        /* Don't bother to list if no useful information will be shown. */
190251881Speter        return SVN_NO_ERROR;
191251881Speter    }
192251881Speter  else
193251881Speter    entryname = path;
194251881Speter
195251881Speter  if (pb->ctx->cancel_func)
196251881Speter    SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
197251881Speter
198251881Speter  if (external_parent_url && external_target)
199251881Speter    {
200251881Speter      if ((pb->last_external_parent_url == NULL
201251881Speter           && pb->last_external_target == NULL)
202251881Speter          || (strcmp(pb->last_external_parent_url, external_parent_url) != 0
203251881Speter              || strcmp(pb->last_external_target, external_target) != 0))
204251881Speter        {
205251881Speter          if (pb->in_external)
206251881Speter            {
207251881Speter              /* The external item being listed is different from the previous
208251881Speter                 one, so close the tag. */
209251881Speter              svn_xml_make_close_tag(&sb, scratch_pool, "external");
210251881Speter              pb->in_external = FALSE;
211251881Speter            }
212251881Speter
213251881Speter          svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "external",
214251881Speter                                "parent_url", external_parent_url,
215251881Speter                                "target", external_target,
216251881Speter                                NULL);
217251881Speter
218251881Speter          pb->last_external_parent_url = external_parent_url;
219251881Speter          pb->last_external_target = external_target;
220251881Speter          pb->in_external = TRUE;
221251881Speter        }
222251881Speter    }
223251881Speter
224251881Speter  svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "entry",
225251881Speter                        "kind", svn_cl__node_kind_str_xml(dirent->kind),
226251881Speter                        NULL);
227251881Speter
228251881Speter  svn_cl__xml_tagged_cdata(&sb, scratch_pool, "name", entryname);
229251881Speter
230251881Speter  if (dirent->kind == svn_node_file)
231251881Speter    {
232251881Speter      svn_cl__xml_tagged_cdata
233251881Speter        (&sb, scratch_pool, "size",
234251881Speter         apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, dirent->size));
235251881Speter    }
236251881Speter
237251881Speter  svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "commit",
238251881Speter                        "revision",
239251881Speter                        apr_psprintf(scratch_pool, "%ld", dirent->created_rev),
240251881Speter                        NULL);
241251881Speter  svn_cl__xml_tagged_cdata(&sb, scratch_pool, "author", dirent->last_author);
242251881Speter  if (dirent->time)
243251881Speter    svn_cl__xml_tagged_cdata(&sb, scratch_pool, "date",
244251881Speter                             svn_time_to_cstring(dirent->time, scratch_pool));
245251881Speter  svn_xml_make_close_tag(&sb, scratch_pool, "commit");
246251881Speter
247251881Speter  if (lock)
248251881Speter    {
249251881Speter      svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "lock", NULL);
250251881Speter      svn_cl__xml_tagged_cdata(&sb, scratch_pool, "token", lock->token);
251251881Speter      svn_cl__xml_tagged_cdata(&sb, scratch_pool, "owner", lock->owner);
252251881Speter      svn_cl__xml_tagged_cdata(&sb, scratch_pool, "comment", lock->comment);
253251881Speter      svn_cl__xml_tagged_cdata(&sb, scratch_pool, "created",
254251881Speter                               svn_time_to_cstring(lock->creation_date,
255251881Speter                                                   scratch_pool));
256251881Speter      if (lock->expiration_date != 0)
257251881Speter        svn_cl__xml_tagged_cdata(&sb, scratch_pool, "expires",
258251881Speter                                 svn_time_to_cstring
259251881Speter                                 (lock->expiration_date, scratch_pool));
260251881Speter      svn_xml_make_close_tag(&sb, scratch_pool, "lock");
261251881Speter    }
262251881Speter
263251881Speter  svn_xml_make_close_tag(&sb, scratch_pool, "entry");
264251881Speter
265251881Speter  return svn_cl__error_checked_fputs(sb->data, stdout);
266251881Speter}
267251881Speter
268251881Speter
269251881Speter/* This implements the `svn_opt_subcommand_t' interface. */
270251881Spetersvn_error_t *
271251881Spetersvn_cl__list(apr_getopt_t *os,
272251881Speter             void *baton,
273251881Speter             apr_pool_t *pool)
274251881Speter{
275251881Speter  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
276251881Speter  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
277251881Speter  apr_array_header_t *targets;
278251881Speter  int i;
279251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
280251881Speter  apr_uint32_t dirent_fields;
281251881Speter  struct print_baton pb;
282251881Speter  svn_boolean_t seen_nonexistent_target = FALSE;
283251881Speter  svn_error_t *err;
284251881Speter  svn_error_t *externals_err = SVN_NO_ERROR;
285251881Speter  struct svn_cl__check_externals_failed_notify_baton nwb;
286251881Speter
287251881Speter  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
288251881Speter                                                      opt_state->targets,
289251881Speter                                                      ctx, FALSE, pool));
290251881Speter
291251881Speter  /* Add "." if user passed 0 arguments */
292251881Speter  svn_opt_push_implicit_dot_target(targets, pool);
293251881Speter
294251881Speter  if (opt_state->xml)
295251881Speter    {
296251881Speter      /* The XML output contains all the information, so "--verbose"
297251881Speter         does not apply. */
298251881Speter      if (opt_state->verbose)
299251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
300251881Speter                                _("'verbose' option invalid in XML mode"));
301251881Speter
302251881Speter      /* If output is not incremental, output the XML header and wrap
303251881Speter         everything in a top-level element. This makes the output in
304251881Speter         its entirety a well-formed XML document. */
305251881Speter      if (! opt_state->incremental)
306251881Speter        SVN_ERR(svn_cl__xml_print_header("lists", pool));
307251881Speter    }
308251881Speter  else
309251881Speter    {
310251881Speter      if (opt_state->incremental)
311251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
312251881Speter                                _("'incremental' option only valid in XML "
313251881Speter                                  "mode"));
314251881Speter    }
315251881Speter
316251881Speter  if (opt_state->verbose || opt_state->xml)
317251881Speter    dirent_fields = SVN_DIRENT_ALL;
318251881Speter  else
319251881Speter    dirent_fields = SVN_DIRENT_KIND; /* the only thing we actually need... */
320251881Speter
321251881Speter  pb.ctx = ctx;
322251881Speter  pb.verbose = opt_state->verbose;
323251881Speter
324251881Speter  if (opt_state->depth == svn_depth_unknown)
325251881Speter    opt_state->depth = svn_depth_immediates;
326251881Speter
327251881Speter  if (opt_state->include_externals)
328251881Speter    {
329251881Speter      nwb.wrapped_func = ctx->notify_func2;
330251881Speter      nwb.wrapped_baton = ctx->notify_baton2;
331251881Speter      nwb.had_externals_error = FALSE;
332251881Speter      ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper;
333251881Speter      ctx->notify_baton2 = &nwb;
334251881Speter    }
335251881Speter
336251881Speter  /* For each target, try to list it. */
337251881Speter  for (i = 0; i < targets->nelts; i++)
338251881Speter    {
339251881Speter      const char *target = APR_ARRAY_IDX(targets, i, const char *);
340251881Speter      const char *truepath;
341251881Speter      svn_opt_revision_t peg_revision;
342251881Speter
343251881Speter      /* Initialize the following variables for
344251881Speter         every list target. */
345251881Speter      pb.last_external_parent_url = NULL;
346251881Speter      pb.last_external_target = NULL;
347251881Speter      pb.in_external = FALSE;
348251881Speter
349251881Speter      svn_pool_clear(subpool);
350251881Speter
351251881Speter      SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
352251881Speter
353251881Speter      /* Get peg revisions. */
354251881Speter      SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
355251881Speter                                 subpool));
356251881Speter
357251881Speter      if (opt_state->xml)
358251881Speter        {
359251881Speter          svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
360251881Speter          svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "list",
361251881Speter                                "path", truepath[0] == '\0' ? "." : truepath,
362251881Speter                                NULL);
363251881Speter          SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
364251881Speter        }
365251881Speter
366251881Speter      err = svn_client_list3(truepath, &peg_revision,
367251881Speter                             &(opt_state->start_revision),
368251881Speter                             opt_state->depth,
369251881Speter                             dirent_fields,
370251881Speter                             (opt_state->xml || opt_state->verbose),
371251881Speter                             opt_state->include_externals,
372251881Speter                             opt_state->xml ? print_dirent_xml : print_dirent,
373251881Speter                             &pb, ctx, subpool);
374251881Speter
375251881Speter      if (err)
376251881Speter        {
377251881Speter          /* If one of the targets is a non-existent URL or wc-entry,
378251881Speter             don't bail out.  Just warn and move on to the next target. */
379251881Speter          if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
380251881Speter              err->apr_err == SVN_ERR_FS_NOT_FOUND)
381251881Speter              svn_handle_warning2(stderr, err, "svn: ");
382251881Speter          else
383251881Speter              return svn_error_trace(err);
384251881Speter
385251881Speter          svn_error_clear(err);
386251881Speter          err = NULL;
387251881Speter          seen_nonexistent_target = TRUE;
388251881Speter        }
389251881Speter
390251881Speter      if (opt_state->xml)
391251881Speter        {
392251881Speter          svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
393251881Speter
394251881Speter          if (pb.in_external)
395251881Speter            {
396251881Speter              /* close the final external item's tag */
397251881Speter              svn_xml_make_close_tag(&sb, pool, "external");
398251881Speter              pb.in_external = FALSE;
399251881Speter            }
400251881Speter
401251881Speter          svn_xml_make_close_tag(&sb, pool, "list");
402251881Speter          SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
403251881Speter        }
404251881Speter    }
405251881Speter
406251881Speter  svn_pool_destroy(subpool);
407251881Speter
408251881Speter  if (opt_state->include_externals && nwb.had_externals_error)
409251881Speter    {
410251881Speter      externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS,
411251881Speter                                       NULL,
412251881Speter                                       _("Failure occurred processing one or "
413251881Speter                                         "more externals definitions"));
414251881Speter    }
415251881Speter
416251881Speter  if (opt_state->xml && ! opt_state->incremental)
417251881Speter    SVN_ERR(svn_cl__xml_print_footer("lists", pool));
418251881Speter
419251881Speter  if (seen_nonexistent_target)
420251881Speter    err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
421251881Speter          _("Could not list all targets because some targets don't exist"));
422251881Speter
423251881Speter  return svn_error_compose_create(externals_err, err);
424251881Speter}
425