]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_delta/path_driver.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / libsvn_delta / path_driver.c
1 /*
2  * path_driver.c -- drive an editor across a set of paths
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 #include <apr_pools.h>
26 #include <apr_strings.h>
27
28 #include "svn_types.h"
29 #include "svn_delta.h"
30 #include "svn_pools.h"
31 #include "svn_dirent_uri.h"
32 #include "svn_path.h"
33 #include "svn_sorts.h"
34 #include "private/svn_fspath.h"
35
36 \f
37 /*** Helper functions. ***/
38
39 typedef struct dir_stack_t
40 {
41   void *dir_baton;   /* the dir baton. */
42   apr_pool_t *pool;  /* the pool associated with the dir baton. */
43
44 } dir_stack_t;
45
46
47 /* Call EDITOR's open_directory() function with the PATH argument, then
48  * add the resulting dir baton to the dir baton stack.
49  */
50 static svn_error_t *
51 open_dir(apr_array_header_t *db_stack,
52          const svn_delta_editor_t *editor,
53          const char *path,
54          apr_pool_t *pool)
55 {
56   void *parent_db, *db;
57   dir_stack_t *item;
58   apr_pool_t *subpool;
59
60   /* Assert that we are in a stable state. */
61   SVN_ERR_ASSERT(db_stack && db_stack->nelts);
62
63   /* Get the parent dir baton. */
64   item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, void *);
65   parent_db = item->dir_baton;
66
67   /* Call the EDITOR's open_directory function to get a new directory
68      baton. */
69   subpool = svn_pool_create(pool);
70   SVN_ERR(editor->open_directory(path, parent_db, SVN_INVALID_REVNUM, subpool,
71                                  &db));
72
73   /* Now add the dir baton to the stack. */
74   item = apr_pcalloc(subpool, sizeof(*item));
75   item->dir_baton = db;
76   item->pool = subpool;
77   APR_ARRAY_PUSH(db_stack, dir_stack_t *) = item;
78
79   return SVN_NO_ERROR;
80 }
81
82
83 /* Pop a directory from the dir baton stack and update the stack
84  * pointer.
85  *
86  * This function calls the EDITOR's close_directory() function.
87  */
88 static svn_error_t *
89 pop_stack(apr_array_header_t *db_stack,
90           const svn_delta_editor_t *editor)
91 {
92   dir_stack_t *item;
93
94   /* Assert that we are in a stable state. */
95   SVN_ERR_ASSERT(db_stack && db_stack->nelts);
96
97   /* Close the most recent directory pushed to the stack. */
98   item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, dir_stack_t *);
99   (void) apr_array_pop(db_stack);
100   SVN_ERR(editor->close_directory(item->dir_baton, item->pool));
101   svn_pool_destroy(item->pool);
102
103   return SVN_NO_ERROR;
104 }
105
106
107 /* Count the number of path components in PATH. */
108 static int
109 count_components(const char *path)
110 {
111   int count = 1;
112   const char *instance = path;
113
114   if ((strlen(path) == 1) && (path[0] == '/'))
115     return 0;
116
117   do
118     {
119       instance++;
120       instance = strchr(instance, '/');
121       if (instance)
122         count++;
123     }
124   while (instance);
125
126   return count;
127 }
128
129
130 \f
131 /*** Public interfaces ***/
132 svn_error_t *
133 svn_delta_path_driver2(const svn_delta_editor_t *editor,
134                        void *edit_baton,
135                        const apr_array_header_t *paths,
136                        svn_boolean_t sort_paths,
137                        svn_delta_path_driver_cb_func_t callback_func,
138                        void *callback_baton,
139                        apr_pool_t *pool)
140 {
141   apr_array_header_t *db_stack = apr_array_make(pool, 4, sizeof(void *));
142   const char *last_path = NULL;
143   int i = 0;
144   void *parent_db = NULL, *db = NULL;
145   const char *path;
146   apr_pool_t *subpool, *iterpool;
147   dir_stack_t *item;
148
149   /* Do nothing if there are no paths. */
150   if (! paths->nelts)
151     return SVN_NO_ERROR;
152
153   subpool = svn_pool_create(pool);
154   iterpool = svn_pool_create(pool);
155
156   /* sort paths if necessary */
157   if (sort_paths && paths->nelts > 1)
158     {
159       apr_array_header_t *sorted = apr_array_copy(subpool, paths);
160       qsort(sorted->elts, sorted->nelts, sorted->elt_size,
161             svn_sort_compare_paths);
162       paths = sorted;
163     }
164
165   item = apr_pcalloc(subpool, sizeof(*item));
166
167   /* If the root of the edit is also a target path, we want to call
168      the callback function to let the user open the root directory and
169      do what needs to be done.  Otherwise, we'll do the open_root()
170      ourselves. */
171   path = APR_ARRAY_IDX(paths, 0, const char *);
172   if (svn_path_is_empty(path))
173     {
174       SVN_ERR(callback_func(&db, NULL, callback_baton, path, subpool));
175       last_path = path;
176       i++;
177     }
178   else
179     {
180       SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM, subpool, &db));
181     }
182   item->pool = subpool;
183   item->dir_baton = db;
184   APR_ARRAY_PUSH(db_stack, void *) = item;
185
186   /* Now, loop over the commit items, traversing the URL tree and
187      driving the editor. */
188   for (; i < paths->nelts; i++)
189     {
190       const char *pdir, *bname;
191       const char *common = "";
192       size_t common_len;
193
194       /* Clear the iteration pool. */
195       svn_pool_clear(iterpool);
196
197       /* Get the next path. */
198       path = APR_ARRAY_IDX(paths, i, const char *);
199
200       /*** Step A - Find the common ancestor of the last path and the
201            current one.  For the first iteration, this is just the
202            empty string. ***/
203       if (i > 0)
204         common = (last_path[0] == '/')
205           ? svn_fspath__get_longest_ancestor(last_path, path, iterpool)
206           : svn_relpath_get_longest_ancestor(last_path, path, iterpool);
207       common_len = strlen(common);
208
209       /*** Step B - Close any directories between the last path and
210            the new common ancestor, if any need to be closed.
211            Sometimes there is nothing to do here (like, for the first
212            iteration, or when the last path was an ancestor of the
213            current one). ***/
214       if ((i > 0) && (strlen(last_path) > common_len))
215         {
216           const char *rel = last_path + (common_len ? (common_len + 1) : 0);
217           int count = count_components(rel);
218           while (count--)
219             {
220               SVN_ERR(pop_stack(db_stack, editor));
221             }
222         }
223
224       /*** Step C - Open any directories between the common ancestor
225            and the parent of the current path. ***/
226       if (*path == '/')
227         svn_fspath__split(&pdir, &bname, path, iterpool);
228       else
229         svn_relpath_split(&pdir, &bname, path, iterpool);
230       if (strlen(pdir) > common_len)
231         {
232           const char *piece = pdir + common_len + 1;
233
234           while (1)
235             {
236               const char *rel = pdir;
237
238               /* Find the first separator. */
239               piece = strchr(piece, '/');
240
241               /* Calculate REL as the portion of PDIR up to (but not
242                  including) the location to which PIECE is pointing. */
243               if (piece)
244                 rel = apr_pstrmemdup(iterpool, pdir, piece - pdir);
245
246               /* Open the subdirectory. */
247               SVN_ERR(open_dir(db_stack, editor, rel, pool));
248
249               /* If we found a '/', advance our PIECE pointer to
250                  character just after that '/'.  Otherwise, we're
251                  done.  */
252               if (piece)
253                 piece++;
254               else
255                 break;
256             }
257         }
258
259       /*** Step D - Tell our caller to handle the current path. ***/
260       item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, void *);
261       parent_db = item->dir_baton;
262       subpool = svn_pool_create(pool);
263       SVN_ERR(callback_func(&db, parent_db, callback_baton, path, subpool));
264       if (db)
265         {
266           item = apr_pcalloc(subpool, sizeof(*item));
267           item->dir_baton = db;
268           item->pool = subpool;
269           APR_ARRAY_PUSH(db_stack, void *) = item;
270         }
271       else
272         {
273           svn_pool_destroy(subpool);
274         }
275
276       /*** Step E - Save our state for the next iteration.  If our
277            caller opened or added PATH as a directory, that becomes
278            our LAST_PATH.  Otherwise, we use PATH's parent
279            directory. ***/
280
281       /* NOTE:  The variable LAST_PATH needs to outlive the loop. */
282       if (db)
283         last_path = path; /* lives in a pool outside our control. */
284       else
285         last_path = apr_pstrdup(pool, pdir); /* duping into POOL. */
286     }
287
288   /* Destroy the iteration subpool. */
289   svn_pool_destroy(iterpool);
290
291   /* Close down any remaining open directory batons. */
292   while (db_stack->nelts)
293     {
294       SVN_ERR(pop_stack(db_stack, editor));
295     }
296
297   return SVN_NO_ERROR;
298 }