1/*
2 * wc_editor.c: editing the local modifications in the WC.
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_pools.h"
36#include "svn_props.h"
37#include "svn_wc.h"
38
39#include <apr_md5.h>
40
41#include "client.h"
42#include "private/svn_subr_private.h"
43#include "private/svn_wc_private.h"
44#include "svn_private_config.h"
45
46
47/* ------------------------------------------------------------------ */
48
49/* WC Modifications Editor.
50 *
51 * This editor applies incoming modifications onto the current working state
52 * of the working copy, to produce a new working state.
53 *
54 * Currently, it assumes the working state matches what the edit driver
55 * expects to find, and may throw an error if not.
56 *
57 * For simplicity, we apply incoming edits as they arrive, rather than
58 * queueing them up to apply in a batch.
59 *
60 * TODO:
61 *   - tests
62 *   - use for all existing scenarios ('svn add', 'svn propset', etc.)
63 *   - Instead of 'root_dir_add' option, probably the driver should anchor
64 *     at the parent dir.
65 *   - Instead of 'ignore_mergeinfo' option, implement that as a wrapper.
66 *   - Option to quietly accept changes that seem to be already applied
67 *     in the versioned state and/or on disk.
68 *     Consider 'svn add' which assumes items to be added are found on disk.
69 *   - Notification.
70 */
71
72/* Everything we need to know about the edit session.
73 */
74struct edit_baton_t
75{
76  const char *anchor_abspath;
77  svn_boolean_t manage_wc_write_lock;
78  const char *lock_root_abspath;  /* the path locked, when locked */
79
80  /* True => 'open_root' method will act as 'add_directory' */
81  svn_boolean_t root_dir_add;
82  /* True => filter out any incoming svn:mergeinfo property changes */
83  svn_boolean_t ignore_mergeinfo_changes;
84
85  svn_ra_session_t *ra_session;
86
87  svn_wc_context_t *wc_ctx;
88  svn_client_ctx_t *ctx;
89  svn_wc_notify_func2_t notify_func;
90  void *notify_baton;
91};
92
93/* Everything we need to know about a directory that's open for edits.
94 */
95struct dir_baton_t
96{
97  apr_pool_t *pool;
98
99  struct edit_baton_t *eb;
100
101  const char *local_abspath;
102};
103
104/* Join PATH onto ANCHOR_ABSPATH.
105 * Throw an error if the result is outside ANCHOR_ABSPATH.
106 */
107static svn_error_t *
108get_path(const char **local_abspath_p,
109         const char *anchor_abspath,
110         const char *path,
111         apr_pool_t *result_pool)
112{
113  svn_boolean_t under_root;
114
115  SVN_ERR(svn_dirent_is_under_root(&under_root, local_abspath_p,
116                                   anchor_abspath, path, result_pool));
117  if (! under_root)
118    {
119      return svn_error_createf(
120                    SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
121                    _("Path '%s' is not in the working copy"),
122                    svn_dirent_local_style(path, result_pool));
123    }
124  return SVN_NO_ERROR;
125}
126
127/* Create a directory on disk and add it to version control,
128 * with no properties.
129 */
130static svn_error_t *
131mkdir(const char *abspath,
132      struct edit_baton_t *eb,
133      apr_pool_t *scratch_pool)
134{
135  SVN_ERR(svn_io_make_dir_recursively(abspath, scratch_pool));
136  SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, abspath,
137                                NULL /*properties*/,
138                                TRUE /* skip checks */,
139                                eb->notify_func, eb->notify_baton,
140                                scratch_pool));
141  return SVN_NO_ERROR;
142}
143
144/* Prepare to open or add a directory: initialize a new dir baton.
145 *
146 * If PATH is "" and PB is null, it represents the root directory of
147 * the edit; otherwise PATH is not "" and PB is not null.
148 */
149static svn_error_t *
150dir_open_or_add(struct dir_baton_t **child_dir_baton,
151                const char *path,
152                struct dir_baton_t *pb,
153                struct edit_baton_t *eb,
154                apr_pool_t *dir_pool)
155{
156  struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
157
158  db->pool = dir_pool;
159  db->eb = eb;
160
161  SVN_ERR(get_path(&db->local_abspath,
162                   eb->anchor_abspath, path, dir_pool));
163
164  *child_dir_baton = db;
165  return SVN_NO_ERROR;
166}
167
168/*  */
169static svn_error_t *
170release_write_lock(struct edit_baton_t *eb,
171                   apr_pool_t *scratch_pool)
172{
173  if (eb->lock_root_abspath)
174    {
175      SVN_ERR(svn_wc__release_write_lock(
176                eb->ctx->wc_ctx, eb->lock_root_abspath, scratch_pool));
177      eb->lock_root_abspath = NULL;
178    }
179  return SVN_NO_ERROR;
180}
181
182/*  */
183static apr_status_t
184pool_cleanup_handler(void *root_baton)
185{
186  struct dir_baton_t *db = root_baton;
187  struct edit_baton_t *eb = db->eb;
188
189  svn_error_clear(release_write_lock(eb, db->pool));
190  return APR_SUCCESS;
191}
192
193/* svn_delta_editor_t function */
194static svn_error_t *
195edit_open(void *edit_baton,
196          svn_revnum_t base_revision,
197          apr_pool_t *result_pool,
198          void **root_baton)
199{
200  struct edit_baton_t *eb = edit_baton;
201  struct dir_baton_t *db;
202
203  SVN_ERR(dir_open_or_add(&db, "", NULL, eb, result_pool));
204
205  /* Acquire a WC write lock */
206  if (eb->manage_wc_write_lock)
207    {
208      apr_pool_cleanup_register(db->pool, db,
209                                pool_cleanup_handler,
210                                apr_pool_cleanup_null);
211      SVN_ERR(svn_wc__acquire_write_lock(&eb->lock_root_abspath,
212                                         eb->ctx->wc_ctx,
213                                         eb->anchor_abspath,
214                                         FALSE /*lock_anchor*/,
215                                         db->pool, db->pool));
216    }
217
218  if (eb->root_dir_add)
219    {
220      SVN_ERR(mkdir(db->local_abspath, eb, result_pool));
221    }
222
223  *root_baton = db;
224  return SVN_NO_ERROR;
225}
226
227/* svn_delta_editor_t function */
228static svn_error_t *
229edit_close_or_abort(void *edit_baton,
230                    apr_pool_t *scratch_pool)
231{
232  SVN_ERR(release_write_lock(edit_baton, scratch_pool));
233  return SVN_NO_ERROR;
234}
235
236/*  */
237static svn_error_t *
238delete_entry(const char *path,
239             svn_revnum_t revision,
240             void *parent_baton,
241             apr_pool_t *scratch_pool)
242{
243  struct dir_baton_t *pb = parent_baton;
244  struct edit_baton_t *eb = pb->eb;
245  const char *local_abspath;
246
247  SVN_ERR(get_path(&local_abspath,
248                   eb->anchor_abspath, path, scratch_pool));
249  SVN_ERR(svn_wc_delete4(eb->wc_ctx, local_abspath,
250                         FALSE /*keep_local*/,
251                         TRUE /*delete_unversioned*/,
252                         NULL, NULL, /*cancellation*/
253                         eb->notify_func, eb->notify_baton, scratch_pool));
254
255  return SVN_NO_ERROR;
256}
257
258/* An svn_delta_editor_t function. */
259static svn_error_t *
260dir_open(const char *path,
261         void *parent_baton,
262         svn_revnum_t base_revision,
263         apr_pool_t *result_pool,
264         void **child_baton)
265{
266  struct dir_baton_t *pb = parent_baton;
267  struct edit_baton_t *eb = pb->eb;
268  struct dir_baton_t *db;
269
270  SVN_ERR(dir_open_or_add(&db, path, pb, eb, result_pool));
271
272  *child_baton = db;
273  return SVN_NO_ERROR;
274}
275
276static svn_error_t *
277dir_add(const char *path,
278        void *parent_baton,
279        const char *copyfrom_path,
280        svn_revnum_t copyfrom_revision,
281        apr_pool_t *result_pool,
282        void **child_baton)
283{
284  struct dir_baton_t *pb = parent_baton;
285  struct edit_baton_t *eb = pb->eb;
286  struct dir_baton_t *db;
287  /* ### Our caller should be providing a scratch pool */
288  apr_pool_t *scratch_pool = svn_pool_create(result_pool);
289
290  SVN_ERR(dir_open_or_add(&db, path, pb, eb, result_pool));
291
292  if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision))
293    {
294      SVN_ERR(svn_client__repos_to_wc_copy_internal(NULL /*timestamp_sleep*/,
295                                           svn_node_dir,
296                                           copyfrom_path,
297                                           copyfrom_revision,
298                                           db->local_abspath,
299                                           db->eb->ra_session,
300                                           db->eb->ctx,
301                                           scratch_pool));
302    }
303  else
304    {
305      SVN_ERR(mkdir(db->local_abspath, eb, result_pool));
306    }
307
308  *child_baton = db;
309  svn_pool_destroy(scratch_pool);
310  return SVN_NO_ERROR;
311}
312
313static svn_error_t *
314dir_change_prop(void *dir_baton,
315                const char *name,
316                const svn_string_t *value,
317                apr_pool_t *scratch_pool)
318{
319  struct dir_baton_t *db = dir_baton;
320  struct edit_baton_t *eb = db->eb;
321
322  if (svn_property_kind2(name) != svn_prop_regular_kind
323      || (eb->ignore_mergeinfo_changes && ! strcmp(name, SVN_PROP_MERGEINFO)))
324    {
325      /* We can't handle DAV, ENTRY and merge specific props here */
326      return SVN_NO_ERROR;
327    }
328
329  SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, db->local_abspath, name, value,
330                           svn_depth_empty, FALSE, NULL,
331                           NULL, NULL, /* Cancellation */
332                           NULL, NULL, /* Notification */
333                           scratch_pool));
334
335  return SVN_NO_ERROR;
336}
337
338static svn_error_t *
339dir_close(void *dir_baton,
340          apr_pool_t *scratch_pool)
341{
342  return SVN_NO_ERROR;
343}
344
345/* Everything we need to know about a file that's open for edits.
346 */
347struct file_baton_t
348{
349  apr_pool_t *pool;
350
351  struct edit_baton_t *eb;
352
353  const char *local_abspath;
354
355  /* fields for the transfer of text changes */
356  const char *writing_file;
357  unsigned char digest[APR_MD5_DIGESTSIZE];  /* MD5 digest of new fulltext */
358  svn_stream_t *wc_file_read_stream, *tmp_file_write_stream;
359  const char *tmp_path;
360};
361
362/* Create a new file on disk and add it to version control.
363 *
364 * The file is empty and has no properties.
365 */
366static svn_error_t *
367mkfile(const char *abspath,
368       struct edit_baton_t *eb,
369       apr_pool_t *scratch_pool)
370{
371  SVN_ERR(svn_io_file_create_empty(abspath, scratch_pool));
372  SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, abspath,
373                                NULL /*properties*/,
374                                TRUE /* skip checks */,
375                                eb->notify_func, eb->notify_baton,
376                                scratch_pool));
377  return SVN_NO_ERROR;
378}
379
380/*  */
381static svn_error_t *
382file_open_or_add(const char *path,
383                 void *parent_baton,
384                 struct file_baton_t **file_baton,
385                 apr_pool_t *file_pool)
386{
387  struct dir_baton_t *pb = parent_baton;
388  struct edit_baton_t *eb = pb->eb;
389  struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb));
390
391  fb->pool = file_pool;
392  fb->eb = eb;
393  SVN_ERR(get_path(&fb->local_abspath,
394                   eb->anchor_abspath, path, fb->pool));
395
396  *file_baton = fb;
397  return SVN_NO_ERROR;
398}
399
400static svn_error_t *
401file_open(const char *path,
402          void *parent_baton,
403          svn_revnum_t base_revision,
404          apr_pool_t *result_pool,
405          void **file_baton)
406{
407  struct file_baton_t *fb;
408
409  SVN_ERR(file_open_or_add(path, parent_baton, &fb, result_pool));
410
411  *file_baton = fb;
412  return SVN_NO_ERROR;
413}
414
415static svn_error_t *
416file_add(const char *path,
417         void *parent_baton,
418         const char *copyfrom_path,
419         svn_revnum_t copyfrom_revision,
420         apr_pool_t *result_pool,
421         void **file_baton)
422{
423  struct file_baton_t *fb;
424
425  SVN_ERR(file_open_or_add(path, parent_baton, &fb, result_pool));
426
427  if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision))
428    {
429      SVN_ERR(svn_client__repos_to_wc_copy_internal(NULL /*timestamp_sleep*/,
430                                           svn_node_file,
431                                           copyfrom_path,
432                                           copyfrom_revision,
433                                           fb->local_abspath,
434                                           fb->eb->ra_session,
435                                           fb->eb->ctx, fb->pool));
436    }
437  else
438    {
439      SVN_ERR(mkfile(fb->local_abspath, fb->eb, result_pool));
440    }
441
442  *file_baton = fb;
443  return SVN_NO_ERROR;
444}
445
446static svn_error_t *
447file_change_prop(void *file_baton,
448                 const char *name,
449                 const svn_string_t *value,
450                 apr_pool_t *scratch_pool)
451{
452  struct file_baton_t *fb = file_baton;
453  struct edit_baton_t *eb = fb->eb;
454
455  if (svn_property_kind2(name) != svn_prop_regular_kind
456      || (eb->ignore_mergeinfo_changes && ! strcmp(name, SVN_PROP_MERGEINFO)))
457    {
458      /* We can't handle DAV, ENTRY and merge specific props here */
459      return SVN_NO_ERROR;
460    }
461
462  SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, fb->local_abspath, name, value,
463                           svn_depth_empty, FALSE, NULL,
464                           NULL, NULL, /* Cancellation */
465                           NULL, NULL, /* Notification */
466                           scratch_pool));
467
468  return SVN_NO_ERROR;
469}
470
471static svn_error_t *
472file_textdelta(void *file_baton,
473               const char *base_checksum,
474               apr_pool_t *result_pool,
475               svn_txdelta_window_handler_t *handler,
476               void **handler_baton)
477{
478  struct file_baton_t *fb = file_baton;
479  const char *target_dir = svn_dirent_dirname(fb->local_abspath, fb->pool);
480  svn_error_t *err;
481
482  SVN_ERR_ASSERT(! fb->writing_file);
483
484  err = svn_stream_open_readonly(&fb->wc_file_read_stream, fb->local_abspath,
485                                 fb->pool, fb->pool);
486  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
487    {
488      svn_error_clear(err);
489      fb->wc_file_read_stream = svn_stream_empty(fb->pool);
490    }
491  else
492    SVN_ERR(err);
493
494  SVN_ERR(svn_stream_open_unique(&fb->tmp_file_write_stream, &fb->writing_file,
495                                 target_dir, svn_io_file_del_none,
496                                 fb->pool, fb->pool));
497
498  svn_txdelta_apply(fb->wc_file_read_stream,
499                    fb->tmp_file_write_stream,
500                    fb->digest,
501                    fb->local_abspath,
502                    fb->pool,
503                    /* Provide the handler directly */
504                    handler, handler_baton);
505
506  return SVN_NO_ERROR;
507}
508
509static svn_error_t *
510file_close(void *file_baton,
511           const char *text_checksum,
512           apr_pool_t *scratch_pool)
513{
514  struct file_baton_t *fb = file_baton;
515
516  /* If we have text changes, write them to disk */
517  if (fb->writing_file)
518    {
519      SVN_ERR(svn_stream_close(fb->wc_file_read_stream));
520      SVN_ERR(svn_io_file_rename2(fb->writing_file, fb->local_abspath,
521                                  FALSE /*flush*/, scratch_pool));
522    }
523
524  if (text_checksum)
525    {
526      svn_checksum_t *expected_checksum;
527      svn_checksum_t *actual_checksum;
528
529      SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
530                                     text_checksum, fb->pool));
531      actual_checksum = svn_checksum__from_digest_md5(fb->digest, fb->pool);
532
533      if (! svn_checksum_match(expected_checksum, actual_checksum))
534        return svn_error_trace(
535                    svn_checksum_mismatch_err(expected_checksum,
536                                              actual_checksum,
537                                              fb->pool,
538                                         _("Checksum mismatch for '%s'"),
539                                              svn_dirent_local_style(
540                                                    fb->local_abspath,
541                                                    fb->pool)));
542    }
543
544  return SVN_NO_ERROR;
545}
546
547svn_error_t *
548svn_client__wc_editor_internal(const svn_delta_editor_t **editor_p,
549                               void **edit_baton_p,
550                               const char *dst_abspath,
551                               svn_boolean_t root_dir_add,
552                               svn_boolean_t ignore_mergeinfo_changes,
553                               svn_boolean_t manage_wc_write_lock,
554                               svn_wc_notify_func2_t notify_func,
555                               void *notify_baton,
556                               svn_ra_session_t *ra_session,
557                               svn_client_ctx_t *ctx,
558                               apr_pool_t *result_pool)
559{
560  svn_delta_editor_t *editor = svn_delta_default_editor(result_pool);
561  struct edit_baton_t *eb = apr_pcalloc(result_pool, sizeof(*eb));
562
563  eb->anchor_abspath = apr_pstrdup(result_pool, dst_abspath);
564  eb->manage_wc_write_lock = manage_wc_write_lock;
565  eb->lock_root_abspath = NULL;
566  eb->root_dir_add = root_dir_add;
567  eb->ignore_mergeinfo_changes = ignore_mergeinfo_changes;
568
569  eb->ra_session = ra_session;
570  eb->wc_ctx = ctx->wc_ctx;
571  eb->ctx = ctx;
572  eb->notify_func = notify_func;
573  eb->notify_baton  = notify_baton;
574
575  editor->open_root = edit_open;
576  editor->close_edit = edit_close_or_abort;
577  editor->abort_edit = edit_close_or_abort;
578
579  editor->delete_entry = delete_entry;
580
581  editor->open_directory = dir_open;
582  editor->add_directory = dir_add;
583  editor->change_dir_prop = dir_change_prop;
584  editor->close_directory = dir_close;
585
586  editor->open_file = file_open;
587  editor->add_file = file_add;
588  editor->change_file_prop = file_change_prop;
589  editor->apply_textdelta = file_textdelta;
590  editor->close_file = file_close;
591
592  *editor_p = editor;
593  *edit_baton_p = eb;
594  return SVN_NO_ERROR;
595}
596
597svn_error_t *
598svn_client__wc_editor(const svn_delta_editor_t **editor_p,
599                      void **edit_baton_p,
600                      const char *dst_abspath,
601                      svn_wc_notify_func2_t notify_func,
602                      void *notify_baton,
603                      svn_ra_session_t *ra_session,
604                      svn_client_ctx_t *ctx,
605                      apr_pool_t *result_pool)
606{
607  SVN_ERR(svn_client__wc_editor_internal(editor_p, edit_baton_p,
608                                         dst_abspath,
609                                         FALSE /*root_dir_add*/,
610                                         FALSE /*ignore_mergeinfo_changes*/,
611                                         TRUE /*manage_wc_write_lock*/,
612                                         notify_func, notify_baton,
613                                         ra_session,
614                                         ctx, result_pool));
615  return SVN_NO_ERROR;
616}
617
618svn_error_t *
619svn_client__wc_copy_mods(const char *src_wc_abspath,
620                         const char *dst_wc_abspath,
621                         svn_wc_notify_func2_t notify_func,
622                         void *notify_baton,
623                         svn_client_ctx_t *ctx,
624                         apr_pool_t *scratch_pool)
625{
626  svn_client__pathrev_t *base;
627  const char *dst_wc_url;
628  svn_ra_session_t *ra_session;
629  const svn_delta_editor_t *editor;
630  void *edit_baton;
631  apr_array_header_t *src_targets = apr_array_make(scratch_pool, 1,
632                                                   sizeof(char *));
633
634  /* We'll need an RA session to obtain the base of any copies */
635  SVN_ERR(svn_client__wc_node_get_base(&base,
636                                       src_wc_abspath, ctx->wc_ctx,
637                                       scratch_pool, scratch_pool));
638  dst_wc_url = base->url;
639  SVN_ERR(svn_client_open_ra_session2(&ra_session,
640                                      dst_wc_url, dst_wc_abspath,
641                                      ctx, scratch_pool, scratch_pool));
642  SVN_ERR(svn_client__wc_editor(&editor, &edit_baton,
643                                dst_wc_abspath,
644                                NULL, NULL, /*notification*/
645                                ra_session, ctx, scratch_pool));
646
647  APR_ARRAY_PUSH(src_targets, const char *) = src_wc_abspath;
648  SVN_ERR(svn_client__wc_replay(src_wc_abspath,
649                                src_targets, svn_depth_infinity, NULL,
650                                editor, edit_baton,
651                                notify_func, notify_baton,
652                                ctx, scratch_pool));
653
654  return SVN_NO_ERROR;
655}
656