1/*
2 * ambient_depth_filter_editor.c -- provide a svn_delta_editor_t which wraps
3 *                                  another editor and provides
4 *                                  *ambient* depth-based filtering
5 *
6 * ====================================================================
7 *    Licensed to the Apache Software Foundation (ASF) under one
8 *    or more contributor license agreements.  See the NOTICE file
9 *    distributed with this work for additional information
10 *    regarding copyright ownership.  The ASF licenses this file
11 *    to you under the Apache License, Version 2.0 (the
12 *    "License"); you may not use this file except in compliance
13 *    with the License.  You may obtain a copy of the License at
14 *
15 *      http://www.apache.org/licenses/LICENSE-2.0
16 *
17 *    Unless required by applicable law or agreed to in writing,
18 *    software distributed under the License is distributed on an
19 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20 *    KIND, either express or implied.  See the License for the
21 *    specific language governing permissions and limitations
22 *    under the License.
23 * ====================================================================
24 */
25
26#include "svn_delta.h"
27#include "svn_wc.h"
28#include "svn_dirent_uri.h"
29#include "svn_path.h"
30
31#include "wc.h"
32
33/*
34     Notes on the general depth-filtering strategy.
35     ==============================================
36
37     When a depth-aware (>= 1.5) client pulls an update from a
38     non-depth-aware server, the server may send back too much data,
39     because it doesn't hear what the client tells it about the
40     "requested depth" of the update (the "foo" in "--depth=foo"), nor
41     about the "ambient depth" of each working copy directory.
42
43     For example, suppose a 1.5 client does this against a 1.4 server:
44
45       $ svn co --depth=empty -rSOME_OLD_REV http://url/repos/blah/ wc
46       $ cd wc
47       $ svn up
48
49     In the initial checkout, the requested depth is 'empty', so the
50     depth-filtering editor (see libsvn_delta/depth_filter_editor.c)
51     that wraps the main update editor transparently filters out all
52     the unwanted calls.
53
54     In the 'svn up', the requested depth is unspecified, meaning that
55     the ambient depth(s) of the working copy should be preserved.
56     Since there's only one directory, and its depth is 'empty',
57     clearly we should filter out or render no-ops all editor calls
58     after open_root(), except maybe for change_dir_prop() on the
59     top-level directory.  (Note that the server will have stuff to
60     send down, because we checked out at an old revision in the first
61     place, to set up this scenario.)
62
63     The depth-filtering editor won't help us here.  It only filters
64     based on the requested depth, it never looks in the working copy
65     to get ambient depths.  So the update editor itself will have to
66     filter out the unwanted calls -- or better yet, it will have to
67     be wrapped in a filtering editor that does the job.
68
69     This is that filtering editor.
70
71     Most of the work is done at the moment of baton construction.
72     When a file or dir is opened, we create its baton with the
73     appropriate ambient depth, either taking the depth directly from
74     the corresponding working copy object (if available), or from its
75     parent baton.  In the latter case, we don't just copy the parent
76     baton's depth, but rather use it to choose the correct depth for
77     this child.  The usual depth demotion rules apply, with the
78     additional stipulation that as soon as we find a subtree is not
79     present at all, due to being omitted for depth reasons, we set the
80     ambiently_excluded flag in its baton, which signals that
81     all descendant batons should be ignored.
82     (In fact, we may just re-use the parent baton, since none of the
83     other fields will be used anyway.)
84
85     See issues #2842 and #2897 for more.
86*/
87
88
89/*** Batons, and the Toys That Create Them ***/
90
91struct edit_baton
92{
93  const svn_delta_editor_t *wrapped_editor;
94  void *wrapped_edit_baton;
95  svn_wc__db_t *db;
96  const char *anchor_abspath;
97  const char *target;
98};
99
100struct file_baton
101{
102  svn_boolean_t ambiently_excluded;
103  struct edit_baton *edit_baton;
104  void *wrapped_baton;
105};
106
107struct dir_baton
108{
109  svn_boolean_t ambiently_excluded;
110  svn_depth_t ambient_depth;
111  struct edit_baton *edit_baton;
112  const char *abspath;
113  void *wrapped_baton;
114};
115
116/* Fetch the STATUS, KIND and DEPTH of the base node at LOCAL_ABSPATH.
117 * If there is no such base node, report 'normal', 'unknown' and 'unknown'
118 * respectively.
119 *
120 * STATUS and/or DEPTH may be NULL if not wanted; KIND must not be NULL.
121 */
122static svn_error_t *
123ambient_read_info(svn_wc__db_status_t *status,
124                  svn_node_kind_t *kind,
125                  svn_depth_t *depth,
126                  svn_wc__db_t *db,
127                  const char *local_abspath,
128                  apr_pool_t *scratch_pool)
129{
130  svn_error_t *err;
131  SVN_ERR_ASSERT(kind != NULL);
132
133  err = svn_wc__db_base_get_info(status, kind, NULL, NULL, NULL, NULL,
134                                 NULL, NULL, NULL, depth, NULL, NULL,
135                                 NULL, NULL, NULL, NULL,
136                                 db, local_abspath,
137                                 scratch_pool, scratch_pool);
138
139  if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
140    {
141      svn_error_clear(err);
142
143      *kind = svn_node_unknown;
144      if (status)
145        *status = svn_wc__db_status_normal;
146      if (depth)
147        *depth = svn_depth_unknown;
148
149      return SVN_NO_ERROR;
150    }
151  else
152    SVN_ERR(err);
153
154  return SVN_NO_ERROR;
155}
156
157/* */
158static svn_error_t *
159make_dir_baton(struct dir_baton **d_p,
160               const char *path,
161               struct edit_baton *eb,
162               struct dir_baton *pb,
163               svn_boolean_t added,
164               apr_pool_t *pool)
165{
166  struct dir_baton *d;
167
168  SVN_ERR_ASSERT(path || (! pb));
169
170  if (pb && pb->ambiently_excluded)
171    {
172      /* Just re-use the parent baton, since the only field that
173         matters is ambiently_excluded. */
174      *d_p = pb;
175      return SVN_NO_ERROR;
176    }
177
178  /* Okay, no easy out, so allocate and initialize a dir baton. */
179  d = apr_pcalloc(pool, sizeof(*d));
180
181  if (path)
182    d->abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
183  else
184    d->abspath = apr_pstrdup(pool, eb->anchor_abspath);
185
186  /* The svn_depth_unknown means that: 1) pb is the anchor; 2) there
187     is an non-null target, for which we are preparing the baton.
188     This enables explicitly pull in the target. */
189  if (pb && pb->ambient_depth != svn_depth_unknown)
190    {
191      svn_boolean_t exclude;
192      svn_wc__db_status_t status;
193      svn_node_kind_t kind;
194      svn_boolean_t exists = TRUE;
195
196      if (!added)
197        {
198          SVN_ERR(ambient_read_info(&status, &kind, NULL,
199                                    eb->db, d->abspath, pool));
200        }
201      else
202        {
203          status = svn_wc__db_status_not_present;
204          kind = svn_node_unknown;
205        }
206
207      exists = (kind != svn_node_unknown);
208
209      if (pb->ambient_depth == svn_depth_empty
210          || pb->ambient_depth == svn_depth_files)
211        {
212          /* This is not a depth upgrade, and the parent directory is
213             depth==empty or depth==files.  So if the parent doesn't
214             already have an entry for the new dir, then the parent
215             doesn't want the new dir at all, thus we should initialize
216             it with ambiently_excluded=TRUE. */
217          exclude = !exists;
218        }
219      else
220        {
221          /* If the parent expect all children by default, only exclude
222             it whenever it is explicitly marked as exclude. */
223          exclude = exists && (status == svn_wc__db_status_excluded);
224        }
225      if (exclude)
226        {
227          d->ambiently_excluded = TRUE;
228          *d_p = d;
229          return SVN_NO_ERROR;
230        }
231    }
232
233  d->edit_baton = eb;
234  /* We'll initialize this differently in add_directory and
235     open_directory. */
236  d->ambient_depth = svn_depth_unknown;
237
238  *d_p = d;
239  return SVN_NO_ERROR;
240}
241
242/* */
243static svn_error_t *
244make_file_baton(struct file_baton **f_p,
245                struct dir_baton *pb,
246                const char *path,
247                svn_boolean_t added,
248                apr_pool_t *pool)
249{
250  struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
251  struct edit_baton *eb = pb->edit_baton;
252  svn_wc__db_status_t status;
253  svn_node_kind_t kind;
254  const char *abspath;
255
256  SVN_ERR_ASSERT(path);
257
258  if (pb->ambiently_excluded)
259    {
260      f->ambiently_excluded = TRUE;
261      *f_p = f;
262      return SVN_NO_ERROR;
263    }
264
265  abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
266
267  if (!added)
268    {
269      SVN_ERR(ambient_read_info(&status, &kind, NULL,
270                                eb->db, abspath, pool));
271    }
272  else
273    {
274      status = svn_wc__db_status_not_present;
275      kind = svn_node_unknown;
276    }
277
278  if (pb->ambient_depth == svn_depth_empty)
279    {
280      /* This is not a depth upgrade, and the parent directory is
281         depth==empty.  So if the parent doesn't
282         already have an entry for the file, then the parent
283         doesn't want to hear about the file at all. */
284
285      if (status == svn_wc__db_status_not_present
286          || status == svn_wc__db_status_server_excluded
287          || status == svn_wc__db_status_excluded
288          || kind == svn_node_unknown)
289        {
290          f->ambiently_excluded = TRUE;
291          *f_p = f;
292          return SVN_NO_ERROR;
293        }
294    }
295
296  /* If pb->ambient_depth == svn_depth_unknown we are pulling
297     in new nodes */
298  if (pb->ambient_depth != svn_depth_unknown
299      && status == svn_wc__db_status_excluded)
300    {
301      f->ambiently_excluded = TRUE;
302      *f_p = f;
303      return SVN_NO_ERROR;
304    }
305
306  f->edit_baton = pb->edit_baton;
307
308  *f_p = f;
309  return SVN_NO_ERROR;
310}
311
312
313/*** Editor Functions ***/
314
315/* An svn_delta_editor_t function. */
316static svn_error_t *
317set_target_revision(void *edit_baton,
318                    svn_revnum_t target_revision,
319                    apr_pool_t *pool)
320{
321  struct edit_baton *eb = edit_baton;
322
323  /* Nothing depth-y to filter here. */
324 return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
325                                                target_revision, pool);
326}
327
328/* An svn_delta_editor_t function. */
329static svn_error_t *
330open_root(void *edit_baton,
331          svn_revnum_t base_revision,
332          apr_pool_t *pool,
333          void **root_baton)
334{
335  struct edit_baton *eb = edit_baton;
336  struct dir_baton *b;
337
338  SVN_ERR(make_dir_baton(&b, NULL, eb, NULL, FALSE, pool));
339  *root_baton = b;
340
341  if (b->ambiently_excluded)
342    return SVN_NO_ERROR;
343
344  if (! *eb->target)
345    {
346      /* For an update with a NULL target, this is equivalent to open_dir(): */
347      svn_node_kind_t kind;
348      svn_wc__db_status_t status;
349      svn_depth_t depth;
350
351      /* Read the depth from the entry. */
352      SVN_ERR(ambient_read_info(&status, &kind, &depth,
353                                eb->db, eb->anchor_abspath,
354                                pool));
355
356      if (kind != svn_node_unknown
357          && status != svn_wc__db_status_not_present
358          && status != svn_wc__db_status_excluded
359          && status != svn_wc__db_status_server_excluded)
360        {
361          b->ambient_depth = depth;
362        }
363    }
364
365  return eb->wrapped_editor->open_root(eb->wrapped_edit_baton, base_revision,
366                                       pool, &b->wrapped_baton);
367}
368
369/* An svn_delta_editor_t function. */
370static svn_error_t *
371delete_entry(const char *path,
372             svn_revnum_t base_revision,
373             void *parent_baton,
374             apr_pool_t *pool)
375{
376  struct dir_baton *pb = parent_baton;
377  struct edit_baton *eb = pb->edit_baton;
378
379  if (pb->ambiently_excluded)
380    return SVN_NO_ERROR;
381
382  if (pb->ambient_depth < svn_depth_immediates)
383    {
384      /* If the entry we want to delete doesn't exist, that's OK.
385         It's probably an old server that doesn't understand
386         depths. */
387      svn_node_kind_t kind;
388      svn_wc__db_status_t status;
389      const char *abspath;
390
391      abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
392
393      SVN_ERR(ambient_read_info(&status, &kind, NULL,
394                                eb->db, abspath, pool));
395
396      if (kind == svn_node_unknown
397          || status == svn_wc__db_status_not_present
398          || status == svn_wc__db_status_excluded
399          || status == svn_wc__db_status_server_excluded)
400        return SVN_NO_ERROR;
401    }
402
403  return eb->wrapped_editor->delete_entry(path, base_revision,
404                                          pb->wrapped_baton, pool);
405}
406
407/* An svn_delta_editor_t function. */
408static svn_error_t *
409add_directory(const char *path,
410              void *parent_baton,
411              const char *copyfrom_path,
412              svn_revnum_t copyfrom_revision,
413              apr_pool_t *pool,
414              void **child_baton)
415{
416  struct dir_baton *pb = parent_baton;
417  struct edit_baton *eb = pb->edit_baton;
418  struct dir_baton *b = NULL;
419
420  SVN_ERR(make_dir_baton(&b, path, eb, pb, TRUE, pool));
421  *child_baton = b;
422
423  if (b->ambiently_excluded)
424    return SVN_NO_ERROR;
425
426  /* It's not excluded, so what should we treat the ambient depth as
427     being? */
428  if (strcmp(eb->target, path) == 0)
429    {
430      /* The target of the edit is being added, so make it
431         infinity. */
432      b->ambient_depth = svn_depth_infinity;
433    }
434  else if (pb->ambient_depth == svn_depth_immediates)
435    {
436      b->ambient_depth = svn_depth_empty;
437    }
438  else
439    {
440      /* There may be a requested depth < svn_depth_infinity, but
441         that's okay, libsvn_delta/depth_filter_editor.c will filter
442         further calls out for us anyway, and the update_editor will
443         do the right thing when it creates the directory. */
444      b->ambient_depth = svn_depth_infinity;
445    }
446
447  return eb->wrapped_editor->add_directory(path, pb->wrapped_baton,
448                                           copyfrom_path,
449                                           copyfrom_revision,
450                                           pool, &b->wrapped_baton);
451}
452
453/* An svn_delta_editor_t function. */
454static svn_error_t *
455open_directory(const char *path,
456               void *parent_baton,
457               svn_revnum_t base_revision,
458               apr_pool_t *pool,
459               void **child_baton)
460{
461  struct dir_baton *pb = parent_baton;
462  struct edit_baton *eb = pb->edit_baton;
463  struct dir_baton *b;
464  const char *local_abspath;
465  svn_node_kind_t kind;
466  svn_wc__db_status_t status;
467  svn_depth_t depth;
468
469  SVN_ERR(make_dir_baton(&b, path, eb, pb, FALSE, pool));
470  *child_baton = b;
471
472  if (b->ambiently_excluded)
473    return SVN_NO_ERROR;
474
475  SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_baton,
476                                             base_revision, pool,
477                                             &b->wrapped_baton));
478  /* Note that for the update editor, the open_directory above will
479     flush the logs of pb's directory, which might be important for
480     this svn_wc_entry call. */
481
482  local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
483
484  SVN_ERR(ambient_read_info(&status, &kind, &depth,
485                            eb->db, local_abspath, pool));
486
487  if (kind != svn_node_unknown
488      && status != svn_wc__db_status_not_present
489      && status != svn_wc__db_status_excluded
490      && status != svn_wc__db_status_server_excluded)
491    {
492      b->ambient_depth = depth;
493    }
494
495  return SVN_NO_ERROR;
496}
497
498/* An svn_delta_editor_t function. */
499static svn_error_t *
500add_file(const char *path,
501         void *parent_baton,
502         const char *copyfrom_path,
503         svn_revnum_t copyfrom_revision,
504         apr_pool_t *pool,
505         void **child_baton)
506{
507  struct dir_baton *pb = parent_baton;
508  struct edit_baton *eb = pb->edit_baton;
509  struct file_baton *b = NULL;
510
511  SVN_ERR(make_file_baton(&b, pb, path, TRUE, pool));
512  *child_baton = b;
513
514  if (b->ambiently_excluded)
515    return SVN_NO_ERROR;
516
517  return eb->wrapped_editor->add_file(path, pb->wrapped_baton,
518                                      copyfrom_path, copyfrom_revision,
519                                      pool, &b->wrapped_baton);
520}
521
522/* An svn_delta_editor_t function. */
523static svn_error_t *
524open_file(const char *path,
525          void *parent_baton,
526          svn_revnum_t base_revision,
527          apr_pool_t *pool,
528          void **child_baton)
529{
530  struct dir_baton *pb = parent_baton;
531  struct edit_baton *eb = pb->edit_baton;
532  struct file_baton *b;
533
534  SVN_ERR(make_file_baton(&b, pb, path, FALSE, pool));
535  *child_baton = b;
536  if (b->ambiently_excluded)
537    return SVN_NO_ERROR;
538
539  return eb->wrapped_editor->open_file(path, pb->wrapped_baton,
540                                       base_revision, pool,
541                                       &b->wrapped_baton);
542}
543
544/* An svn_delta_editor_t function. */
545static svn_error_t *
546apply_textdelta(void *file_baton,
547                const char *base_checksum,
548                apr_pool_t *pool,
549                svn_txdelta_window_handler_t *handler,
550                void **handler_baton)
551{
552  struct file_baton *fb = file_baton;
553  struct edit_baton *eb = fb->edit_baton;
554
555  /* For filtered files, we just consume the textdelta. */
556  if (fb->ambiently_excluded)
557    {
558      *handler = svn_delta_noop_window_handler;
559      *handler_baton = NULL;
560      return SVN_NO_ERROR;
561    }
562
563  return eb->wrapped_editor->apply_textdelta(fb->wrapped_baton,
564                                             base_checksum, pool,
565                                             handler, handler_baton);
566}
567
568/* An svn_delta_editor_t function. */
569static svn_error_t *
570close_file(void *file_baton,
571           const char *text_checksum,
572           apr_pool_t *pool)
573{
574  struct file_baton *fb = file_baton;
575  struct edit_baton *eb = fb->edit_baton;
576
577  if (fb->ambiently_excluded)
578    return SVN_NO_ERROR;
579
580  return eb->wrapped_editor->close_file(fb->wrapped_baton,
581                                        text_checksum, pool);
582}
583
584/* An svn_delta_editor_t function. */
585static svn_error_t *
586absent_file(const char *path,
587            void *parent_baton,
588            apr_pool_t *pool)
589{
590  struct dir_baton *pb = parent_baton;
591  struct edit_baton *eb = pb->edit_baton;
592
593  if (pb->ambiently_excluded)
594    return SVN_NO_ERROR;
595
596  return eb->wrapped_editor->absent_file(path, pb->wrapped_baton, pool);
597}
598
599/* An svn_delta_editor_t function. */
600static svn_error_t *
601close_directory(void *dir_baton,
602                apr_pool_t *pool)
603{
604  struct dir_baton *db = dir_baton;
605  struct edit_baton *eb = db->edit_baton;
606
607  if (db->ambiently_excluded)
608    return SVN_NO_ERROR;
609
610  return eb->wrapped_editor->close_directory(db->wrapped_baton, pool);
611}
612
613/* An svn_delta_editor_t function. */
614static svn_error_t *
615absent_directory(const char *path,
616                 void *parent_baton,
617                 apr_pool_t *pool)
618{
619  struct dir_baton *pb = parent_baton;
620  struct edit_baton *eb = pb->edit_baton;
621
622  /* Don't report absent items in filtered directories. */
623  if (pb->ambiently_excluded)
624    return SVN_NO_ERROR;
625
626  return eb->wrapped_editor->absent_directory(path, pb->wrapped_baton, pool);
627}
628
629/* An svn_delta_editor_t function. */
630static svn_error_t *
631change_file_prop(void *file_baton,
632                 const char *name,
633                 const svn_string_t *value,
634                 apr_pool_t *pool)
635{
636  struct file_baton *fb = file_baton;
637  struct edit_baton *eb = fb->edit_baton;
638
639  if (fb->ambiently_excluded)
640    return SVN_NO_ERROR;
641
642  return eb->wrapped_editor->change_file_prop(fb->wrapped_baton,
643                                              name, value, pool);
644}
645
646/* An svn_delta_editor_t function. */
647static svn_error_t *
648change_dir_prop(void *dir_baton,
649                const char *name,
650                const svn_string_t *value,
651                apr_pool_t *pool)
652{
653  struct dir_baton *db = dir_baton;
654  struct edit_baton *eb = db->edit_baton;
655
656  if (db->ambiently_excluded)
657    return SVN_NO_ERROR;
658
659  return eb->wrapped_editor->change_dir_prop(db->wrapped_baton,
660                                             name, value, pool);
661}
662
663/* An svn_delta_editor_t function. */
664static svn_error_t *
665close_edit(void *edit_baton,
666           apr_pool_t *pool)
667{
668  struct edit_baton *eb = edit_baton;
669  return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool);
670}
671
672svn_error_t *
673svn_wc__ambient_depth_filter_editor(const svn_delta_editor_t **editor,
674                                    void **edit_baton,
675                                    svn_wc__db_t *db,
676                                    const char *anchor_abspath,
677                                    const char *target,
678                                    const svn_delta_editor_t *wrapped_editor,
679                                    void *wrapped_edit_baton,
680                                    apr_pool_t *result_pool)
681{
682  svn_delta_editor_t *depth_filter_editor;
683  struct edit_baton *eb;
684
685  SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath));
686
687  depth_filter_editor = svn_delta_default_editor(result_pool);
688  depth_filter_editor->set_target_revision = set_target_revision;
689  depth_filter_editor->open_root = open_root;
690  depth_filter_editor->delete_entry = delete_entry;
691  depth_filter_editor->add_directory = add_directory;
692  depth_filter_editor->open_directory = open_directory;
693  depth_filter_editor->change_dir_prop = change_dir_prop;
694  depth_filter_editor->close_directory = close_directory;
695  depth_filter_editor->absent_directory = absent_directory;
696  depth_filter_editor->add_file = add_file;
697  depth_filter_editor->open_file = open_file;
698  depth_filter_editor->apply_textdelta = apply_textdelta;
699  depth_filter_editor->change_file_prop = change_file_prop;
700  depth_filter_editor->close_file = close_file;
701  depth_filter_editor->absent_file = absent_file;
702  depth_filter_editor->close_edit = close_edit;
703
704  eb = apr_pcalloc(result_pool, sizeof(*eb));
705  eb->wrapped_editor = wrapped_editor;
706  eb->wrapped_edit_baton = wrapped_edit_baton;
707  eb->db = db;
708  eb->anchor_abspath = anchor_abspath;
709  eb->target = target;
710
711  *editor = depth_filter_editor;
712  *edit_baton = eb;
713
714  return SVN_NO_ERROR;
715}
716