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"
37 /*** Helper functions. ***/
39 typedef struct dir_stack_t
41 void *dir_baton; /* the dir baton. */
42 apr_pool_t *pool; /* the pool associated with the dir baton. */
47 /* Call EDITOR's open_directory() function with the PATH argument, then
48 * add the resulting dir baton to the dir baton stack.
51 open_dir(apr_array_header_t *db_stack,
52 const svn_delta_editor_t *editor,
60 /* Assert that we are in a stable state. */
61 SVN_ERR_ASSERT(db_stack && db_stack->nelts);
63 /* Get the parent dir baton. */
64 item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, void *);
65 parent_db = item->dir_baton;
67 /* Call the EDITOR's open_directory function to get a new directory
69 subpool = svn_pool_create(pool);
70 SVN_ERR(editor->open_directory(path, parent_db, SVN_INVALID_REVNUM, subpool,
73 /* Now add the dir baton to the stack. */
74 item = apr_pcalloc(subpool, sizeof(*item));
77 APR_ARRAY_PUSH(db_stack, dir_stack_t *) = item;
83 /* Pop a directory from the dir baton stack and update the stack
86 * This function calls the EDITOR's close_directory() function.
89 pop_stack(apr_array_header_t *db_stack,
90 const svn_delta_editor_t *editor)
94 /* Assert that we are in a stable state. */
95 SVN_ERR_ASSERT(db_stack && db_stack->nelts);
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);
107 /* Count the number of path components in PATH. */
109 count_components(const char *path)
112 const char *instance = path;
114 if ((strlen(path) == 1) && (path[0] == '/'))
120 instance = strchr(instance, '/');
131 /*** Public interfaces ***/
133 svn_delta_path_driver2(const svn_delta_editor_t *editor,
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,
141 apr_array_header_t *db_stack = apr_array_make(pool, 4, sizeof(void *));
142 const char *last_path = NULL;
144 void *parent_db = NULL, *db = NULL;
146 apr_pool_t *subpool, *iterpool;
149 /* Do nothing if there are no paths. */
153 subpool = svn_pool_create(pool);
154 iterpool = svn_pool_create(pool);
156 /* sort paths if necessary */
157 if (sort_paths && paths->nelts > 1)
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);
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++)
190 const char *pdir, *bname;
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 svn_fspath__split(&pdir, &bname, path, iterpool);
229 svn_relpath_split(&pdir, &bname, path, iterpool);
230 if (strlen(pdir) > common_len)
232 const char *piece = pdir + common_len + 1;
236 const char *rel = pdir;
238 /* Find the first separator. */
239 piece = strchr(piece, '/');
241 /* Calculate REL as the portion of PDIR up to (but not
242 including) the location to which PIECE is pointing. */
244 rel = apr_pstrmemdup(iterpool, pdir, piece - pdir);
246 /* Open the subdirectory. */
247 SVN_ERR(open_dir(db_stack, editor, rel, pool));
249 /* If we found a '/', advance our PIECE pointer to
250 character just after that '/'. Otherwise, we're
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));
266 item = apr_pcalloc(subpool, sizeof(*item));
267 item->dir_baton = db;
268 item->pool = subpool;
269 APR_ARRAY_PUSH(db_stack, void *) = item;
273 svn_pool_destroy(subpool);
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
281 /* NOTE: The variable LAST_PATH needs to outlive the loop. */
283 last_path = path; /* lives in a pool outside our control. */
285 last_path = apr_pstrdup(pool, pdir); /* duping into POOL. */
288 /* Destroy the iteration subpool. */
289 svn_pool_destroy(iterpool);
291 /* Close down any remaining open directory batons. */
292 while (db_stack->nelts)
294 SVN_ERR(pop_stack(db_stack, editor));