1/*
2 * copy_foreign.c:  copy from other repository support.
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/*** Includes. ***/
27
28#include <string.h>
29#include "svn_hash.h"
30#include "svn_client.h"
31#include "svn_delta.h"
32#include "svn_dirent_uri.h"
33#include "svn_error.h"
34#include "svn_error_codes.h"
35#include "svn_path.h"
36#include "svn_pools.h"
37#include "svn_props.h"
38#include "svn_ra.h"
39#include "svn_wc.h"
40
41#include <apr_md5.h>
42
43#include "client.h"
44#include "private/svn_subr_private.h"
45#include "private/svn_wc_private.h"
46#include "svn_private_config.h"
47
48struct edit_baton_t
49{
50  apr_pool_t *pool;
51  const char *anchor_abspath;
52
53  svn_wc_context_t *wc_ctx;
54  svn_wc_notify_func2_t notify_func;
55  void *notify_baton;
56};
57
58struct dir_baton_t
59{
60  apr_pool_t *pool;
61
62  struct dir_baton_t *pb;
63  struct edit_baton_t *eb;
64
65  const char *local_abspath;
66
67  svn_boolean_t created;
68  apr_hash_t *properties;
69
70  int users;
71};
72
73/* svn_delta_editor_t function */
74static svn_error_t *
75edit_open(void *edit_baton,
76          svn_revnum_t base_revision,
77          apr_pool_t *result_pool,
78          void **root_baton)
79{
80  struct edit_baton_t *eb = edit_baton;
81  apr_pool_t *dir_pool = svn_pool_create(eb->pool);
82  struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
83
84  db->pool = dir_pool;
85  db->eb = eb;
86  db->users = 1;
87  db->local_abspath = eb->anchor_abspath;
88
89  SVN_ERR(svn_io_make_dir_recursively(eb->anchor_abspath, dir_pool));
90
91  *root_baton = db;
92
93  return SVN_NO_ERROR;
94}
95
96/* svn_delta_editor_t function */
97static svn_error_t *
98edit_close(void *edit_baton,
99           apr_pool_t *scratch_pool)
100{
101  return SVN_NO_ERROR;
102}
103
104static svn_error_t *
105dir_add(const char *path,
106        void *parent_baton,
107        const char *copyfrom_path,
108        svn_revnum_t copyfrom_revision,
109        apr_pool_t *result_pool,
110        void **child_baton)
111{
112  struct dir_baton_t *pb = parent_baton;
113  struct edit_baton_t *eb = pb->eb;
114  apr_pool_t *dir_pool = svn_pool_create(pb->pool);
115  struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
116  svn_boolean_t under_root;
117
118  pb->users++;
119
120  db->pb = pb;
121  db->eb = pb->eb;
122  db->pool = dir_pool;
123  db->users = 1;
124
125  SVN_ERR(svn_dirent_is_under_root(&under_root, &db->local_abspath,
126                                   eb->anchor_abspath, path, db->pool));
127  if (! under_root)
128    {
129      return svn_error_createf(
130                    SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
131                    _("Path '%s' is not in the working copy"),
132                    svn_dirent_local_style(path, db->pool));
133    }
134
135  SVN_ERR(svn_io_make_dir_recursively(db->local_abspath, db->pool));
136
137  *child_baton = db;
138  return SVN_NO_ERROR;
139}
140
141static svn_error_t *
142dir_change_prop(void *dir_baton,
143                const char *name,
144                const svn_string_t *value,
145                apr_pool_t *scratch_pool)
146{
147  struct dir_baton_t *db = dir_baton;
148  struct edit_baton_t *eb = db->eb;
149  svn_prop_kind_t prop_kind;
150
151  prop_kind = svn_property_kind2(name);
152
153  if (prop_kind != svn_prop_regular_kind
154      || ! strcmp(name, SVN_PROP_MERGEINFO))
155    {
156      /* We can't handle DAV, ENTRY and merge specific props here */
157      return SVN_NO_ERROR;
158    }
159
160  if (! db->created)
161    {
162      /* We can still store them in the hash for immediate addition
163         with the svn_wc_add_from_disk3() call */
164      if (! db->properties)
165        db->properties = apr_hash_make(db->pool);
166
167      if (value != NULL)
168        svn_hash_sets(db->properties, apr_pstrdup(db->pool, name),
169                      svn_string_dup(value, db->pool));
170    }
171  else
172    {
173      /* We have already notified for this directory, so don't do that again */
174      SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, db->local_abspath, name, value,
175                               svn_depth_empty, FALSE, NULL,
176                               NULL, NULL, /* Cancellation */
177                               NULL, NULL, /* Notification */
178                               scratch_pool));
179    }
180
181  return SVN_NO_ERROR;
182}
183
184/* Releases the directory baton if there are no more users */
185static svn_error_t *
186maybe_done(struct dir_baton_t *db)
187{
188  db->users--;
189
190  if (db->users == 0)
191    {
192      struct dir_baton_t *pb = db->pb;
193
194      svn_pool_clear(db->pool);
195
196      if (pb)
197        SVN_ERR(maybe_done(pb));
198    }
199
200  return SVN_NO_ERROR;
201}
202
203static svn_error_t *
204ensure_added(struct dir_baton_t *db,
205             apr_pool_t *scratch_pool)
206{
207  if (db->created)
208    return SVN_NO_ERROR;
209
210  if (db->pb)
211    SVN_ERR(ensure_added(db->pb, scratch_pool));
212
213  db->created = TRUE;
214
215  /* Add the directory with all the already collected properties */
216  SVN_ERR(svn_wc_add_from_disk3(db->eb->wc_ctx,
217                                db->local_abspath,
218                                db->properties,
219                                TRUE /* skip checks */,
220                                db->eb->notify_func,
221                                db->eb->notify_baton,
222                                scratch_pool));
223
224  return SVN_NO_ERROR;
225}
226
227static svn_error_t *
228dir_close(void *dir_baton,
229          apr_pool_t *scratch_pool)
230{
231  struct dir_baton_t *db = dir_baton;
232  /*struct edit_baton_t *eb = db->eb;*/
233
234  SVN_ERR(ensure_added(db, scratch_pool));
235
236  SVN_ERR(maybe_done(db));
237
238  return SVN_NO_ERROR;
239}
240
241struct file_baton_t
242{
243  apr_pool_t *pool;
244
245  struct dir_baton_t *pb;
246  struct edit_baton_t *eb;
247
248  const char *local_abspath;
249  apr_hash_t *properties;
250
251  svn_boolean_t writing;
252  unsigned char digest[APR_MD5_DIGESTSIZE];
253
254  const char *tmp_path;
255};
256
257static svn_error_t *
258file_add(const char *path,
259         void *parent_baton,
260         const char *copyfrom_path,
261         svn_revnum_t copyfrom_revision,
262         apr_pool_t *result_pool,
263         void **file_baton)
264{
265  struct dir_baton_t *pb = parent_baton;
266  struct edit_baton_t *eb = pb->eb;
267  apr_pool_t *file_pool = svn_pool_create(pb->pool);
268  struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb));
269  svn_boolean_t under_root;
270
271  pb->users++;
272
273  fb->pool = file_pool;
274  fb->eb = eb;
275  fb->pb = pb;
276
277  SVN_ERR(svn_dirent_is_under_root(&under_root, &fb->local_abspath,
278                                   eb->anchor_abspath, path, fb->pool));
279  if (! under_root)
280    {
281      return svn_error_createf(
282                    SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
283                    _("Path '%s' is not in the working copy"),
284                    svn_dirent_local_style(path, fb->pool));
285    }
286
287  *file_baton = fb;
288  return SVN_NO_ERROR;
289}
290
291static svn_error_t *
292file_change_prop(void *file_baton,
293                 const char *name,
294                 const svn_string_t *value,
295                 apr_pool_t *scratch_pool)
296{
297  struct file_baton_t *fb = file_baton;
298  svn_prop_kind_t prop_kind;
299
300  prop_kind = svn_property_kind2(name);
301
302  if (prop_kind != svn_prop_regular_kind
303      || ! strcmp(name, SVN_PROP_MERGEINFO))
304    {
305      /* We can't handle DAV, ENTRY and merge specific props here */
306      return SVN_NO_ERROR;
307    }
308
309  /* We store all properties in the hash for immediate addition
310      with the svn_wc_add_from_disk3() call */
311  if (! fb->properties)
312    fb->properties = apr_hash_make(fb->pool);
313
314  if (value != NULL)
315    svn_hash_sets(fb->properties, apr_pstrdup(fb->pool, name),
316                  svn_string_dup(value, fb->pool));
317
318  return SVN_NO_ERROR;
319}
320
321static svn_error_t *
322file_textdelta(void *file_baton,
323               const char *base_checksum,
324               apr_pool_t *result_pool,
325               svn_txdelta_window_handler_t *handler,
326               void **handler_baton)
327{
328  struct file_baton_t *fb = file_baton;
329  svn_stream_t *target;
330
331  SVN_ERR_ASSERT(! fb->writing);
332
333  SVN_ERR(svn_stream_open_writable(&target, fb->local_abspath, fb->pool,
334                                   fb->pool));
335
336  fb->writing = TRUE;
337  svn_txdelta_apply(svn_stream_empty(fb->pool) /* source */,
338                    target,
339                    fb->digest,
340                    fb->local_abspath,
341                    fb->pool,
342                    /* Provide the handler directly */
343                    handler, handler_baton);
344
345  return SVN_NO_ERROR;
346}
347
348static svn_error_t *
349file_close(void *file_baton,
350           const char *text_checksum,
351           apr_pool_t *scratch_pool)
352{
353  struct file_baton_t *fb = file_baton;
354  struct edit_baton_t *eb = fb->eb;
355  struct dir_baton_t *pb = fb->pb;
356
357  SVN_ERR(ensure_added(pb, fb->pool));
358
359  if (text_checksum)
360    {
361      svn_checksum_t *expected_checksum;
362      svn_checksum_t *actual_checksum;
363
364      SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
365                                     text_checksum, fb->pool));
366      actual_checksum = svn_checksum__from_digest_md5(fb->digest, fb->pool);
367
368      if (! svn_checksum_match(expected_checksum, actual_checksum))
369        return svn_error_trace(
370                    svn_checksum_mismatch_err(expected_checksum,
371                                              actual_checksum,
372                                              fb->pool,
373                                         _("Checksum mismatch for '%s'"),
374                                              svn_dirent_local_style(
375                                                    fb->local_abspath,
376                                                    fb->pool)));
377    }
378
379  SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, fb->local_abspath, fb->properties,
380                                TRUE /* skip checks */,
381                                eb->notify_func, eb->notify_baton,
382                                fb->pool));
383
384  svn_pool_destroy(fb->pool);
385  SVN_ERR(maybe_done(pb));
386
387  return SVN_NO_ERROR;
388}
389
390static svn_error_t *
391copy_foreign_dir(svn_ra_session_t *ra_session,
392                 svn_client__pathrev_t *location,
393                 svn_wc_context_t *wc_ctx,
394                 const char *dst_abspath,
395                 svn_depth_t depth,
396                 svn_wc_notify_func2_t notify_func,
397                 void *notify_baton,
398                 svn_cancel_func_t cancel_func,
399                 void *cancel_baton,
400                 apr_pool_t *scratch_pool)
401{
402  struct edit_baton_t eb;
403  svn_delta_editor_t *editor = svn_delta_default_editor(scratch_pool);
404  const svn_delta_editor_t *wrapped_editor;
405  void *wrapped_baton;
406  const svn_ra_reporter3_t *reporter;
407  void *reporter_baton;
408
409  eb.pool = scratch_pool;
410  eb.anchor_abspath = dst_abspath;
411
412  eb.wc_ctx = wc_ctx;
413  eb.notify_func = notify_func;
414  eb.notify_baton  = notify_baton;
415
416  editor->open_root = edit_open;
417  editor->close_edit = edit_close;
418
419  editor->add_directory = dir_add;
420  editor->change_dir_prop = dir_change_prop;
421  editor->close_directory = dir_close;
422
423  editor->add_file = file_add;
424  editor->change_file_prop = file_change_prop;
425  editor->apply_textdelta = file_textdelta;
426  editor->close_file = file_close;
427
428  SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
429                                            editor, &eb,
430                                            &wrapped_editor, &wrapped_baton,
431                                            scratch_pool));
432
433  SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &reporter_baton,
434                            location->rev, "", svn_depth_infinity,
435                            FALSE, FALSE, wrapped_editor, wrapped_baton,
436                            scratch_pool, scratch_pool));
437
438  SVN_ERR(reporter->set_path(reporter_baton, "", location->rev, depth,
439                             TRUE /* incomplete */,
440                             NULL, scratch_pool));
441
442  SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
443
444  return SVN_NO_ERROR;
445}
446
447
448svn_error_t *
449svn_client__copy_foreign(const char *url,
450                         const char *dst_abspath,
451                         svn_opt_revision_t *peg_revision,
452                         svn_opt_revision_t *revision,
453                         svn_depth_t depth,
454                         svn_boolean_t make_parents,
455                         svn_boolean_t already_locked,
456                         svn_client_ctx_t *ctx,
457                         apr_pool_t *scratch_pool)
458{
459  svn_ra_session_t *ra_session;
460  svn_client__pathrev_t *loc;
461  svn_node_kind_t kind;
462  svn_node_kind_t wc_kind;
463  const char *dir_abspath;
464
465  SVN_ERR_ASSERT(svn_path_is_url(url));
466  SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
467
468  /* Do we need to validate/update revisions? */
469
470  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
471                                            url, NULL,
472                                            peg_revision,
473                                            revision, ctx,
474                                            scratch_pool));
475
476  SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, scratch_pool));
477
478  if (kind != svn_node_file && kind != svn_node_dir)
479    return svn_error_createf(
480                SVN_ERR_ILLEGAL_TARGET, NULL,
481                _("'%s' is not a valid location inside a repository"),
482                url);
483
484  SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dst_abspath, FALSE, TRUE,
485                            scratch_pool));
486
487  if (wc_kind != svn_node_none)
488    {
489      return svn_error_createf(
490                SVN_ERR_ENTRY_EXISTS, NULL,
491                _("'%s' is already under version control"),
492                svn_dirent_local_style(dst_abspath, scratch_pool));
493    }
494
495  dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
496  SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath,
497                            FALSE, FALSE, scratch_pool));
498
499  if (wc_kind == svn_node_none)
500    {
501      if (make_parents)
502        SVN_ERR(svn_client__make_local_parents(dir_abspath, make_parents, ctx,
503                                               scratch_pool));
504
505      SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath,
506                                FALSE, FALSE, scratch_pool));
507    }
508
509  if (wc_kind != svn_node_dir)
510    return svn_error_createf(
511                SVN_ERR_ENTRY_NOT_FOUND, NULL,
512                _("Can't add '%s', because no parent directory is found"),
513                svn_dirent_local_style(dst_abspath, scratch_pool));
514
515
516  if (kind == svn_node_file)
517    {
518      svn_stream_t *target;
519      apr_hash_t *props;
520      apr_hash_index_t *hi;
521      SVN_ERR(svn_stream_open_writable(&target, dst_abspath, scratch_pool,
522                                       scratch_pool));
523
524      SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, target, NULL, &props,
525                              scratch_pool));
526
527      if (props != NULL)
528        for (hi = apr_hash_first(scratch_pool, props); hi;
529             hi = apr_hash_next(hi))
530          {
531            const char *name = apr_hash_this_key(hi);
532
533            if (svn_property_kind2(name) != svn_prop_regular_kind
534                || ! strcmp(name, SVN_PROP_MERGEINFO))
535              {
536                /* We can't handle DAV, ENTRY and merge specific props here */
537                svn_hash_sets(props, name, NULL);
538              }
539          }
540
541      if (!already_locked)
542        SVN_WC__CALL_WITH_WRITE_LOCK(
543              svn_wc_add_from_disk3(ctx->wc_ctx, dst_abspath, props,
544                                    TRUE /* skip checks */,
545                                    ctx->notify_func2, ctx->notify_baton2,
546                                    scratch_pool),
547              ctx->wc_ctx, dir_abspath, FALSE, scratch_pool);
548      else
549        SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, dst_abspath, props,
550                                      TRUE /* skip checks */,
551                                      ctx->notify_func2, ctx->notify_baton2,
552                                      scratch_pool));
553    }
554  else
555    {
556      if (!already_locked)
557        SVN_WC__CALL_WITH_WRITE_LOCK(
558              copy_foreign_dir(ra_session, loc,
559                               ctx->wc_ctx, dst_abspath,
560                               depth,
561                               ctx->notify_func2, ctx->notify_baton2,
562                               ctx->cancel_func, ctx->cancel_baton,
563                               scratch_pool),
564              ctx->wc_ctx, dir_abspath, FALSE, scratch_pool);
565      else
566        SVN_ERR(copy_foreign_dir(ra_session, loc,
567                                 ctx->wc_ctx, dst_abspath,
568                                 depth,
569                                 ctx->notify_func2, ctx->notify_baton2,
570                                 ctx->cancel_func, ctx->cancel_baton,
571                                 scratch_pool));
572    }
573
574  return SVN_NO_ERROR;
575}
576