2 * path_driver.c -- drive an editor across a set of paths
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
25 #include <apr_pools.h>
26 #include <apr_strings.h>
28 #include "svn_types.h"
29 #include "svn_delta.h"
30 #include "svn_pools.h"
31 #include "svn_dirent_uri.h"
33 #include "svn_sorts.h"
34 #include "private/svn_fspath.h"
35 #include "private/svn_sorts_private.h"
38 /*** Helper functions. ***/
40 typedef struct dir_stack_t
42 void *dir_baton; /* the dir baton. */
43 apr_pool_t *pool; /* the pool associated with the dir baton. */
48 /* Call EDITOR's open_directory() function with the PATH argument, then
49 * add the resulting dir baton to the dir baton stack.
52 open_dir(apr_array_header_t *db_stack,
53 const svn_delta_editor_t *editor,
61 /* Assert that we are in a stable state. */
62 SVN_ERR_ASSERT(db_stack && db_stack->nelts);
64 /* Get the parent dir baton. */
65 item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, void *);
66 parent_db = item->dir_baton;
68 /* Call the EDITOR's open_directory function to get a new directory
70 subpool = svn_pool_create(pool);
71 SVN_ERR(editor->open_directory(path, parent_db, SVN_INVALID_REVNUM, subpool,
74 /* Now add the dir baton to the stack. */
75 item = apr_pcalloc(subpool, sizeof(*item));
78 APR_ARRAY_PUSH(db_stack, dir_stack_t *) = item;
84 /* Pop a directory from the dir baton stack and update the stack
87 * This function calls the EDITOR's close_directory() function.
90 pop_stack(apr_array_header_t *db_stack,
91 const svn_delta_editor_t *editor)
95 /* Assert that we are in a stable state. */
96 SVN_ERR_ASSERT(db_stack && db_stack->nelts);
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);
108 /* Count the number of path components in PATH. */
110 count_components(const char *path)
113 const char *instance = path;
115 if ((strlen(path) == 1) && (path[0] == '/'))
121 instance = strchr(instance, '/');
132 /*** Public interfaces ***/
134 svn_delta_path_driver2(const svn_delta_editor_t *editor,
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,
142 apr_array_header_t *db_stack = apr_array_make(pool, 4, sizeof(void *));
143 const char *last_path = NULL;
145 void *parent_db = NULL, *db = NULL;
147 apr_pool_t *subpool, *iterpool;
150 /* Do nothing if there are no paths. */
154 subpool = svn_pool_create(pool);
155 iterpool = svn_pool_create(pool);
157 /* sort paths if necessary */
158 if (sort_paths && paths->nelts > 1)
160 apr_array_header_t *sorted = apr_array_copy(subpool, paths);
161 svn_sort__array(sorted, svn_sort_compare_paths);
165 item = apr_pcalloc(subpool, sizeof(*item));
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()
171 path = APR_ARRAY_IDX(paths, 0, const char *);
172 if (svn_path_is_empty(path))
174 SVN_ERR(callback_func(&db, NULL, callback_baton, path, subpool));
180 SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM, subpool, &db));
182 item->pool = subpool;
183 item->dir_baton = db;
184 APR_ARRAY_PUSH(db_stack, void *) = item;
186 /* Now, loop over the commit items, traversing the URL tree and
187 driving the editor. */
188 for (; i < paths->nelts; i++)
191 const char *common = "";
194 /* Clear the iteration pool. */
195 svn_pool_clear(iterpool);
197 /* Get the next path. */
198 path = APR_ARRAY_IDX(paths, i, const char *);
200 /*** Step A - Find the common ancestor of the last path and the
201 current one. For the first iteration, this is just the
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);
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
214 if ((i > 0) && (strlen(last_path) > common_len))
216 const char *rel = last_path + (common_len ? (common_len + 1) : 0);
217 int count = count_components(rel);
220 SVN_ERR(pop_stack(db_stack, editor));
224 /*** Step C - Open any directories between the common ancestor
225 and the parent of the current path. ***/
227 pdir = svn_fspath__dirname(path, iterpool);
229 pdir = svn_relpath_dirname(path, iterpool);
231 if (strlen(pdir) > common_len)
233 const char *piece = pdir + common_len + 1;
237 const char *rel = pdir;
239 /* Find the first separator. */
240 piece = strchr(piece, '/');
242 /* Calculate REL as the portion of PDIR up to (but not
243 including) the location to which PIECE is pointing. */
245 rel = apr_pstrmemdup(iterpool, pdir, piece - pdir);
247 /* Open the subdirectory. */
248 SVN_ERR(open_dir(db_stack, editor, rel, pool));
250 /* If we found a '/', advance our PIECE pointer to
251 character just after that '/'. Otherwise, we're
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));
267 item = apr_pcalloc(subpool, sizeof(*item));
268 item->dir_baton = db;
269 item->pool = subpool;
270 APR_ARRAY_PUSH(db_stack, void *) = item;
274 svn_pool_destroy(subpool);
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
282 /* NOTE: The variable LAST_PATH needs to outlive the loop. */
284 last_path = path; /* lives in a pool outside our control. */
286 last_path = apr_pstrdup(pool, pdir); /* duping into POOL. */
289 /* Destroy the iteration subpool. */
290 svn_pool_destroy(iterpool);
292 /* Close down any remaining open directory batons. */
293 while (db_stack->nelts)
295 SVN_ERR(pop_stack(db_stack, editor));