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 #include "private/svn_sorts_private.h"
36
37
38 /*** Helper functions. ***/
39
40 typedef struct dir_stack_t
41 {
42 void *dir_baton; /* the dir baton. */
43 apr_pool_t *pool; /* the pool associated with the dir baton. */
44
45 } dir_stack_t;
46
47
48 /* Call EDITOR's open_directory() function with the PATH argument, then
49 * add the resulting dir baton to the dir baton stack.
50 */
51 static svn_error_t *
open_dir(apr_array_header_t * db_stack,const svn_delta_editor_t * editor,const char * path,apr_pool_t * pool)52 open_dir(apr_array_header_t *db_stack,
53 const svn_delta_editor_t *editor,
54 const char *path,
55 apr_pool_t *pool)
56 {
57 void *parent_db, *db;
58 dir_stack_t *item;
59 apr_pool_t *subpool;
60
61 /* Assert that we are in a stable state. */
62 SVN_ERR_ASSERT(db_stack && db_stack->nelts);
63
64 /* Get the parent dir baton. */
65 item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, void *);
66 parent_db = item->dir_baton;
67
68 /* Call the EDITOR's open_directory function to get a new directory
69 baton. */
70 subpool = svn_pool_create(pool);
71 SVN_ERR(editor->open_directory(path, parent_db, SVN_INVALID_REVNUM, subpool,
72 &db));
73
74 /* Now add the dir baton to the stack. */
75 item = apr_pcalloc(subpool, sizeof(*item));
76 item->dir_baton = db;
77 item->pool = subpool;
78 APR_ARRAY_PUSH(db_stack, dir_stack_t *) = item;
79
80 return SVN_NO_ERROR;
81 }
82
83
84 /* Pop a directory from the dir baton stack and update the stack
85 * pointer.
86 *
87 * This function calls the EDITOR's close_directory() function.
88 */
89 static svn_error_t *
pop_stack(apr_array_header_t * db_stack,const svn_delta_editor_t * editor)90 pop_stack(apr_array_header_t *db_stack,
91 const svn_delta_editor_t *editor)
92 {
93 dir_stack_t *item;
94
95 /* Assert that we are in a stable state. */
96 SVN_ERR_ASSERT(db_stack && db_stack->nelts);
97
98 /* Close the most recent directory pushed to the stack. */
99 item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, dir_stack_t *);
100 (void) apr_array_pop(db_stack);
101 SVN_ERR(editor->close_directory(item->dir_baton, item->pool));
102 svn_pool_destroy(item->pool);
103
104 return SVN_NO_ERROR;
105 }
106
107
108 /* Count the number of path components in PATH. */
109 static int
count_components(const char * path)110 count_components(const char *path)
111 {
112 int count = 1;
113 const char *instance = path;
114
115 if ((strlen(path) == 1) && (path[0] == '/'))
116 return 0;
117
118 do
119 {
120 instance++;
121 instance = strchr(instance, '/');
122 if (instance)
123 count++;
124 }
125 while (instance);
126
127 return count;
128 }
129
130
131
132 /*** Public interfaces ***/
133 svn_error_t *
svn_delta_path_driver2(const svn_delta_editor_t * editor,void * edit_baton,const apr_array_header_t * paths,svn_boolean_t sort_paths,svn_delta_path_driver_cb_func_t callback_func,void * callback_baton,apr_pool_t * pool)134 svn_delta_path_driver2(const svn_delta_editor_t *editor,
135 void *edit_baton,
136 const apr_array_header_t *paths,
137 svn_boolean_t sort_paths,
138 svn_delta_path_driver_cb_func_t callback_func,
139 void *callback_baton,
140 apr_pool_t *pool)
141 {
142 apr_array_header_t *db_stack = apr_array_make(pool, 4, sizeof(void *));
143 const char *last_path = NULL;
144 int i = 0;
145 void *parent_db = NULL, *db = NULL;
146 const char *path;
147 apr_pool_t *subpool, *iterpool;
148 dir_stack_t *item;
149
150 /* Do nothing if there are no paths. */
151 if (! paths->nelts)
152 return SVN_NO_ERROR;
153
154 subpool = svn_pool_create(pool);
155 iterpool = svn_pool_create(pool);
156
157 /* sort paths if necessary */
158 if (sort_paths && paths->nelts > 1)
159 {
160 apr_array_header_t *sorted = apr_array_copy(subpool, paths);
161 svn_sort__array(sorted, 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;
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 pdir = svn_fspath__dirname(path, iterpool);
228 else
229 pdir = svn_relpath_dirname(path, iterpool);
230
231 if (strlen(pdir) > common_len)
232 {
233 const char *piece = pdir + common_len + 1;
234
235 while (1)
236 {
237 const char *rel = pdir;
238
239 /* Find the first separator. */
240 piece = strchr(piece, '/');
241
242 /* Calculate REL as the portion of PDIR up to (but not
243 including) the location to which PIECE is pointing. */
244 if (piece)
245 rel = apr_pstrmemdup(iterpool, pdir, piece - pdir);
246
247 /* Open the subdirectory. */
248 SVN_ERR(open_dir(db_stack, editor, rel, pool));
249
250 /* If we found a '/', advance our PIECE pointer to
251 character just after that '/'. Otherwise, we're
252 done. */
253 if (piece)
254 piece++;
255 else
256 break;
257 }
258 }
259
260 /*** Step D - Tell our caller to handle the current path. ***/
261 item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, void *);
262 parent_db = item->dir_baton;
263 subpool = svn_pool_create(pool);
264 SVN_ERR(callback_func(&db, parent_db, callback_baton, path, subpool));
265 if (db)
266 {
267 item = apr_pcalloc(subpool, sizeof(*item));
268 item->dir_baton = db;
269 item->pool = subpool;
270 APR_ARRAY_PUSH(db_stack, void *) = item;
271 }
272 else
273 {
274 svn_pool_destroy(subpool);
275 }
276
277 /*** Step E - Save our state for the next iteration. If our
278 caller opened or added PATH as a directory, that becomes
279 our LAST_PATH. Otherwise, we use PATH's parent
280 directory. ***/
281
282 /* NOTE: The variable LAST_PATH needs to outlive the loop. */
283 if (db)
284 last_path = path; /* lives in a pool outside our control. */
285 else
286 last_path = apr_pstrdup(pool, pdir); /* duping into POOL. */
287 }
288
289 /* Destroy the iteration subpool. */
290 svn_pool_destroy(iterpool);
291
292 /* Close down any remaining open directory batons. */
293 while (db_stack->nelts)
294 {
295 SVN_ERR(pop_stack(db_stack, editor));
296 }
297
298 return SVN_NO_ERROR;
299 }
300