]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_wc/ambient_depth_filter_editor.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_wc / ambient_depth_filter_editor.c
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 \f
89 /*** Batons, and the Toys That Create Them ***/
90
91 struct 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
100 struct file_baton
101 {
102   svn_boolean_t ambiently_excluded;
103   struct edit_baton *edit_baton;
104   void *wrapped_baton;
105 };
106
107 struct 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  */
122 static svn_error_t *
123 ambient_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 /* */
158 static svn_error_t *
159 make_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 /* */
243 static svn_error_t *
244 make_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 \f
313 /*** Editor Functions ***/
314
315 /* An svn_delta_editor_t function. */
316 static svn_error_t *
317 set_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. */
329 static svn_error_t *
330 open_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. */
370 static svn_error_t *
371 delete_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. */
408 static svn_error_t *
409 add_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. */
454 static svn_error_t *
455 open_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. */
499 static svn_error_t *
500 add_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. */
523 static svn_error_t *
524 open_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. */
545 static svn_error_t *
546 apply_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. */
569 static svn_error_t *
570 close_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. */
585 static svn_error_t *
586 absent_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. */
600 static svn_error_t *
601 close_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. */
614 static svn_error_t *
615 absent_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. */
630 static svn_error_t *
631 change_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. */
647 static svn_error_t *
648 change_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. */
664 static svn_error_t *
665 close_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
672 svn_error_t *
673 svn_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 }