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