path_driver.c revision 289180
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 40typedef 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 */ 51static svn_error_t * 52open_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 */ 89static svn_error_t * 90pop_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. */ 109static int 110count_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 ***/ 133svn_error_t * 134svn_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