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_sorts_private.h" 35 36 37/*** Helper functions. ***/ 38 39typedef 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/* Push onto dir_stack a new item allocated in POOL and containing 48 * DIR_BATON and POOL. 49 */ 50static void 51push_dir_stack_item(apr_array_header_t *db_stack, 52 void *dir_baton, 53 apr_pool_t *pool) 54{ 55 dir_stack_t *item = apr_pcalloc(pool, sizeof(*item)); 56 57 item->dir_baton = dir_baton; 58 item->pool = pool; 59 APR_ARRAY_PUSH(db_stack, dir_stack_t *) = item; 60} 61 62 63/* Call EDITOR's open_directory() function with the PATH argument, then 64 * add the resulting dir baton to the dir baton stack. 65 */ 66static svn_error_t * 67open_dir(apr_array_header_t *db_stack, 68 const svn_delta_editor_t *editor, 69 const char *path, 70 apr_pool_t *pool) 71{ 72 void *parent_db, *db; 73 dir_stack_t *item; 74 apr_pool_t *subpool; 75 76 /* Assert that we are in a stable state. */ 77 SVN_ERR_ASSERT(db_stack && db_stack->nelts); 78 79 /* Get the parent dir baton. */ 80 item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, void *); 81 parent_db = item->dir_baton; 82 83 /* Call the EDITOR's open_directory function to get a new directory 84 baton. */ 85 subpool = svn_pool_create(pool); 86 SVN_ERR(editor->open_directory(path, parent_db, SVN_INVALID_REVNUM, subpool, 87 &db)); 88 89 /* Now add the dir baton to the stack. */ 90 push_dir_stack_item(db_stack, db, subpool); 91 92 return SVN_NO_ERROR; 93} 94 95 96/* Pop a directory from the dir baton stack and update the stack 97 * pointer. 98 * 99 * This function calls the EDITOR's close_directory() function. 100 */ 101static svn_error_t * 102pop_stack(apr_array_header_t *db_stack, 103 const svn_delta_editor_t *editor) 104{ 105 dir_stack_t *item; 106 107 /* Assert that we are in a stable state. */ 108 SVN_ERR_ASSERT(db_stack && db_stack->nelts); 109 110 /* Close the most recent directory pushed to the stack. */ 111 item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, dir_stack_t *); 112 (void) apr_array_pop(db_stack); 113 SVN_ERR(editor->close_directory(item->dir_baton, item->pool)); 114 svn_pool_destroy(item->pool); 115 116 return SVN_NO_ERROR; 117} 118 119 120/* Count the number of path components in PATH. */ 121static int 122count_components(const char *path) 123{ 124 int count = 1; 125 const char *instance = path; 126 127 if ((strlen(path) == 1) && (path[0] == '/')) 128 return 0; 129 130 do 131 { 132 instance++; 133 instance = strchr(instance, '/'); 134 if (instance) 135 count++; 136 } 137 while (instance); 138 139 return count; 140} 141 142 143 144/*** Public interfaces ***/ 145svn_error_t * 146svn_delta_path_driver3(const svn_delta_editor_t *editor, 147 void *edit_baton, 148 const apr_array_header_t *relpaths, 149 svn_boolean_t sort_paths, 150 svn_delta_path_driver_cb_func2_t callback_func, 151 void *callback_baton, 152 apr_pool_t *pool) 153{ 154 svn_delta_path_driver_state_t *state; 155 int i; 156 apr_pool_t *subpool, *iterpool; 157 158 /* Do nothing if there are no paths. */ 159 if (! relpaths->nelts) 160 return SVN_NO_ERROR; 161 162 subpool = svn_pool_create(pool); 163 iterpool = svn_pool_create(pool); 164 165 /* sort paths if necessary */ 166 if (sort_paths && relpaths->nelts > 1) 167 { 168 apr_array_header_t *sorted = apr_array_copy(subpool, relpaths); 169 svn_sort__array(sorted, svn_sort_compare_paths); 170 relpaths = sorted; 171 } 172 173 SVN_ERR(svn_delta_path_driver_start(&state, 174 editor, edit_baton, 175 callback_func, callback_baton, 176 pool)); 177 178 /* Now, loop over the commit items, traversing the URL tree and 179 driving the editor. */ 180 for (i = 0; i < relpaths->nelts; i++) 181 { 182 const char *relpath; 183 184 /* Clear the iteration pool. */ 185 svn_pool_clear(iterpool); 186 187 /* Get the next path. */ 188 relpath = APR_ARRAY_IDX(relpaths, i, const char *); 189 190 SVN_ERR(svn_delta_path_driver_step(state, relpath, iterpool)); 191 } 192 193 /* Destroy the iteration subpool. */ 194 svn_pool_destroy(iterpool); 195 196 SVN_ERR(svn_delta_path_driver_finish(state, pool)); 197 198 return SVN_NO_ERROR; 199} 200 201struct svn_delta_path_driver_state_t 202{ 203 const svn_delta_editor_t *editor; 204 void *edit_baton; 205 svn_delta_path_driver_cb_func2_t callback_func; 206 void *callback_baton; 207 apr_array_header_t *db_stack; 208 const char *last_path; 209 apr_pool_t *pool; /* at least the lifetime of the entire drive */ 210}; 211 212svn_error_t * 213svn_delta_path_driver_start(svn_delta_path_driver_state_t **state_p, 214 const svn_delta_editor_t *editor, 215 void *edit_baton, 216 svn_delta_path_driver_cb_func2_t callback_func, 217 void *callback_baton, 218 apr_pool_t *pool) 219{ 220 svn_delta_path_driver_state_t *state = apr_pcalloc(pool, sizeof(*state)); 221 222 state->editor = editor; 223 state->edit_baton = edit_baton; 224 state->callback_func = callback_func; 225 state->callback_baton = callback_baton; 226 state->db_stack = apr_array_make(pool, 4, sizeof(void *)); 227 state->last_path = NULL; 228 state->pool = pool; 229 230 *state_p = state; 231 return SVN_NO_ERROR; 232} 233 234svn_error_t * 235svn_delta_path_driver_step(svn_delta_path_driver_state_t *state, 236 const char *relpath, 237 apr_pool_t *scratch_pool) 238{ 239 const char *pdir; 240 const char *common = ""; 241 size_t common_len; 242 apr_pool_t *subpool; 243 dir_stack_t *item; 244 void *parent_db, *db; 245 246 SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); 247 248 /* If the first target path is not the root of the edit, we must first 249 call open_root() ourselves. (If the first target path is the root of 250 the edit, then we expect the user's callback to do so.) */ 251 if (!state->last_path && !svn_path_is_empty(relpath)) 252 { 253 subpool = svn_pool_create(state->pool); 254 SVN_ERR(state->editor->open_root(state->edit_baton, SVN_INVALID_REVNUM, 255 subpool, &db)); 256 push_dir_stack_item(state->db_stack, db, subpool); 257 } 258 259 /*** Step A - Find the common ancestor of the last path and the 260 current one. For the first iteration, this is just the 261 empty string. ***/ 262 if (state->last_path) 263 common = svn_relpath_get_longest_ancestor(state->last_path, relpath, 264 scratch_pool); 265 common_len = strlen(common); 266 267 /*** Step B - Close any directories between the last path and 268 the new common ancestor, if any need to be closed. 269 Sometimes there is nothing to do here (like, for the first 270 iteration, or when the last path was an ancestor of the 271 current one). ***/ 272 if ((state->last_path) && (strlen(state->last_path) > common_len)) 273 { 274 const char *rel = state->last_path + (common_len ? (common_len + 1) : 0); 275 int count = count_components(rel); 276 while (count--) 277 { 278 SVN_ERR(pop_stack(state->db_stack, state->editor)); 279 } 280 } 281 282 /*** Step C - Open any directories between the common ancestor 283 and the parent of the current path. ***/ 284 pdir = svn_relpath_dirname(relpath, scratch_pool); 285 286 if (strlen(pdir) > common_len) 287 { 288 const char *piece = pdir + common_len + 1; 289 290 while (1) 291 { 292 const char *rel = pdir; 293 294 /* Find the first separator. */ 295 piece = strchr(piece, '/'); 296 297 /* Calculate REL as the portion of PDIR up to (but not 298 including) the location to which PIECE is pointing. */ 299 if (piece) 300 rel = apr_pstrmemdup(scratch_pool, pdir, piece - pdir); 301 302 /* Open the subdirectory. */ 303 SVN_ERR(open_dir(state->db_stack, state->editor, rel, state->pool)); 304 305 /* If we found a '/', advance our PIECE pointer to 306 character just after that '/'. Otherwise, we're 307 done. */ 308 if (piece) 309 piece++; 310 else 311 break; 312 } 313 } 314 315 /*** Step D - Tell our caller to handle the current path. ***/ 316 if (state->db_stack->nelts) 317 { 318 item = APR_ARRAY_IDX(state->db_stack, state->db_stack->nelts - 1, void *); 319 parent_db = item->dir_baton; 320 } 321 else 322 parent_db = NULL; 323 db = NULL; /* predictable behaviour for callbacks that don't set it */ 324 subpool = svn_pool_create(state->pool); 325 SVN_ERR(state->callback_func(&db, 326 state->editor, state->edit_baton, parent_db, 327 state->callback_baton, 328 relpath, subpool)); 329 if (db) 330 { 331 push_dir_stack_item(state->db_stack, db, subpool); 332 } 333 else 334 { 335 svn_pool_destroy(subpool); 336 } 337 338 /*** Step E - Save our state for the next iteration. If our 339 caller opened or added PATH as a directory, that becomes 340 our LAST_PATH. Otherwise, we use PATH's parent 341 directory. ***/ 342 state->last_path = apr_pstrdup(state->pool, db ? relpath : pdir); 343 344 return SVN_NO_ERROR; 345} 346 347svn_error_t * 348svn_delta_path_driver_finish(svn_delta_path_driver_state_t *state, 349 apr_pool_t *scratch_pool) 350{ 351 /* Close down any remaining open directory batons. */ 352 while (state->db_stack->nelts) 353 { 354 SVN_ERR(pop_stack(state->db_stack, state->editor)); 355 } 356 357 return SVN_NO_ERROR; 358} 359