1/*
2 * export-cmd.c -- Subversion export command
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* ==================================================================== */
25
26
27
28/*** Includes. ***/
29
30#include "svn_client.h"
31#include "svn_error.h"
32#include "svn_dirent_uri.h"
33#include "svn_path.h"
34#include "svn_cmdline.h"
35#include "cl.h"
36
37#include "svn_private_config.h"
38#include "private/svn_string_private.h"
39#include "private/svn_client_private.h"
40
41/*** The export editor code. ***/
42
43/* ---------------------------------------------------------------------- */
44
45/*** A dedicated 'export' editor, which does no .svn/ accounting.  ***/
46
47typedef struct edit_baton_t
48{
49  apr_int64_t file_count;
50  apr_int64_t dir_count;
51  apr_int64_t byte_count;
52  apr_int64_t prop_count;
53  apr_int64_t prop_byte_count;
54} edit_baton_t;
55
56static svn_error_t *
57set_target_revision(void *edit_baton,
58                    svn_revnum_t target_revision,
59                    apr_pool_t *pool)
60{
61  return SVN_NO_ERROR;
62}
63
64
65/* Just ensure that the main export directory exists. */
66static svn_error_t *
67open_root(void *edit_baton,
68          svn_revnum_t base_revision,
69          apr_pool_t *pool,
70          void **root_baton)
71{
72  *root_baton = edit_baton;
73  return SVN_NO_ERROR;
74}
75
76
77/* Ensure the directory exists, and send feedback. */
78static svn_error_t *
79add_directory(const char *path,
80              void *parent_baton,
81              const char *copyfrom_path,
82              svn_revnum_t copyfrom_revision,
83              apr_pool_t *pool,
84              void **baton)
85{
86  edit_baton_t *eb = parent_baton;
87  eb->dir_count++;
88
89  *baton = parent_baton;
90  return SVN_NO_ERROR;
91}
92
93
94/* Build a file baton. */
95static svn_error_t *
96add_file(const char *path,
97          void *parent_baton,
98          const char *copyfrom_path,
99          svn_revnum_t copyfrom_revision,
100          apr_pool_t *pool,
101          void **baton)
102{
103  edit_baton_t *eb = parent_baton;
104  eb->file_count++;
105
106  *baton = parent_baton;
107  return SVN_NO_ERROR;
108}
109
110static svn_error_t *
111window_handler(svn_txdelta_window_t *window, void *baton)
112{
113  edit_baton_t *eb = baton;
114  if (window != NULL)
115    eb->byte_count += window->tview_len;
116
117  return SVN_NO_ERROR;
118}
119
120/* Write incoming data into the tmpfile stream */
121
122static svn_error_t *
123apply_textdelta(void *file_baton,
124                const char *base_checksum,
125                apr_pool_t *pool,
126                svn_txdelta_window_handler_t *handler,
127                void **handler_baton)
128{
129  *handler_baton = file_baton;
130  *handler = window_handler;
131
132  return SVN_NO_ERROR;
133}
134
135static svn_error_t *
136change_file_prop(void *file_baton,
137                 const char *name,
138                 const svn_string_t *value,
139                 apr_pool_t *pool)
140{
141  edit_baton_t *eb = file_baton;
142  eb->prop_count++;
143  eb->prop_byte_count += value->len;
144
145  return SVN_NO_ERROR;
146}
147
148static svn_error_t *
149change_dir_prop(void *dir_baton,
150                const char *name,
151                const svn_string_t *value,
152                apr_pool_t *pool)
153{
154  edit_baton_t *eb = dir_baton;
155  eb->prop_count++;
156
157  return SVN_NO_ERROR;
158}
159
160static svn_error_t *
161close_file(void *file_baton,
162           const char *text_checksum,
163           apr_pool_t *pool)
164{
165  return SVN_NO_ERROR;
166}
167
168
169/*** Public Interfaces ***/
170
171static svn_error_t *
172bench_null_export(svn_revnum_t *result_rev,
173                  const char *from_path_or_url,
174                  svn_opt_revision_t *peg_revision,
175                  svn_opt_revision_t *revision,
176                  svn_depth_t depth,
177                  void *baton,
178                  svn_client_ctx_t *ctx,
179                  svn_boolean_t quiet,
180                  apr_pool_t *pool)
181{
182  svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
183  svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
184
185  SVN_ERR_ASSERT(peg_revision != NULL);
186  SVN_ERR_ASSERT(revision != NULL);
187
188  if (peg_revision->kind == svn_opt_revision_unspecified)
189    peg_revision->kind = svn_path_is_url(from_path_or_url)
190                       ? svn_opt_revision_head
191                       : svn_opt_revision_working;
192
193  if (revision->kind == svn_opt_revision_unspecified)
194    revision = peg_revision;
195
196  if (from_is_url || ! SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind))
197    {
198      svn_client__pathrev_t *loc;
199      svn_ra_session_t *ra_session;
200      svn_node_kind_t kind;
201
202      /* Get the RA connection. */
203      SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
204                                                from_path_or_url, NULL,
205                                                peg_revision,
206                                                revision, ctx, pool));
207
208      SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, pool));
209
210      if (kind == svn_node_file)
211        {
212          apr_hash_t *props;
213
214          /* Since you cannot actually root an editor at a file, we
215           * manually drive a few functions of our editor. */
216
217          /* Step outside the editor-likeness for a moment, to actually talk
218           * to the repository. */
219          /* ### note: the stream will not be closed */
220          SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
221                                  svn_stream_empty(pool),
222                                  NULL, &props, pool));
223        }
224      else if (kind == svn_node_dir)
225        {
226          void *edit_baton = NULL;
227          const svn_delta_editor_t *export_editor = NULL;
228          const svn_ra_reporter3_t *reporter;
229          void *report_baton;
230
231          svn_delta_editor_t *editor = svn_delta_default_editor(pool);
232
233          editor->set_target_revision = set_target_revision;
234          editor->open_root = open_root;
235          editor->add_directory = add_directory;
236          editor->add_file = add_file;
237          editor->apply_textdelta = apply_textdelta;
238          editor->close_file = close_file;
239          editor->change_file_prop = change_file_prop;
240          editor->change_dir_prop = change_dir_prop;
241
242          /* for ra_svn, we don't need an editior in quiet mode */
243          if (!quiet || strncmp(loc->repos_root_url, "svn:", 4))
244            SVN_ERR(svn_delta_get_cancellation_editor(ctx->cancel_func,
245                                                      ctx->cancel_baton,
246                                                      editor,
247                                                      baton,
248                                                      &export_editor,
249                                                      &edit_baton,
250                                                      pool));
251
252          /* Manufacture a basic 'report' to the update reporter. */
253          SVN_ERR(svn_ra_do_update3(ra_session,
254                                    &reporter, &report_baton,
255                                    loc->rev,
256                                    "", /* no sub-target */
257                                    depth,
258                                    FALSE, /* don't want copyfrom-args */
259                                    FALSE, /* don't want ignore_ancestry */
260                                    export_editor, edit_baton,
261                                    pool, pool));
262
263          SVN_ERR(reporter->set_path(report_baton, "", loc->rev,
264                                     /* Depth is irrelevant, as we're
265                                        passing start_empty=TRUE anyway. */
266                                     svn_depth_infinity,
267                                     TRUE, /* "help, my dir is empty!" */
268                                     NULL, pool));
269
270          SVN_ERR(reporter->finish_report(report_baton, pool));
271        }
272      else if (kind == svn_node_none)
273        {
274          return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
275                                   _("URL '%s' doesn't exist"),
276                                   from_path_or_url);
277        }
278      /* kind == svn_node_unknown not handled */
279    }
280
281
282  if (result_rev)
283    *result_rev = edit_revision;
284
285  return SVN_NO_ERROR;
286}
287
288
289/*** Code. ***/
290
291/* This implements the `svn_opt_subcommand_t' interface. */
292svn_error_t *
293svn_cl__null_export(apr_getopt_t *os,
294                    void *baton,
295                    apr_pool_t *pool)
296{
297  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
298  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
299  const char *from;
300  apr_array_header_t *targets;
301  svn_error_t *err;
302  svn_opt_revision_t peg_revision;
303  const char *truefrom;
304  edit_baton_t eb = { 0 };
305
306  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
307                                                      opt_state->targets,
308                                                      ctx, FALSE, pool));
309
310  /* We want exactly 1 or 2 targets for this subcommand. */
311  if (targets->nelts < 1)
312    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
313  if (targets->nelts > 2)
314    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
315
316  /* The first target is the `from' path. */
317  from = APR_ARRAY_IDX(targets, 0, const char *);
318
319  /* Get the peg revision if present. */
320  SVN_ERR(svn_opt_parse_path(&peg_revision, &truefrom, from, pool));
321
322  if (opt_state->depth == svn_depth_unknown)
323    opt_state->depth = svn_depth_infinity;
324
325  /* Do the export. */
326  err = bench_null_export(NULL, truefrom, &peg_revision,
327                          &(opt_state->start_revision),
328                          opt_state->depth,
329                          &eb,
330                          ctx, opt_state->quiet, pool);
331
332  if (!opt_state->quiet)
333    SVN_ERR(svn_cmdline_printf(pool,
334                               _("%15s directories\n"
335                                 "%15s files\n"
336                                 "%15s bytes in files\n"
337                                 "%15s properties\n"
338                                 "%15s bytes in properties\n"),
339                               svn__ui64toa_sep(eb.dir_count, ',', pool),
340                               svn__ui64toa_sep(eb.file_count, ',', pool),
341                               svn__ui64toa_sep(eb.byte_count, ',', pool),
342                               svn__ui64toa_sep(eb.prop_count, ',', pool),
343                               svn__ui64toa_sep(eb.prop_byte_count, ',', pool)));
344
345  return svn_error_trace(err);
346}
347