1251881Speter/* 2251881Speter * path_driver.c -- drive an editor across a set of paths 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter#include <apr_pools.h> 26251881Speter#include <apr_strings.h> 27251881Speter 28251881Speter#include "svn_types.h" 29251881Speter#include "svn_delta.h" 30251881Speter#include "svn_pools.h" 31251881Speter#include "svn_dirent_uri.h" 32251881Speter#include "svn_path.h" 33251881Speter#include "svn_sorts.h" 34289180Speter#include "private/svn_sorts_private.h" 35251881Speter 36251881Speter 37251881Speter/*** Helper functions. ***/ 38251881Speter 39251881Spetertypedef struct dir_stack_t 40251881Speter{ 41251881Speter void *dir_baton; /* the dir baton. */ 42251881Speter apr_pool_t *pool; /* the pool associated with the dir baton. */ 43251881Speter 44251881Speter} dir_stack_t; 45251881Speter 46251881Speter 47362181Sdim/* Push onto dir_stack a new item allocated in POOL and containing 48362181Sdim * DIR_BATON and POOL. 49362181Sdim */ 50362181Sdimstatic void 51362181Sdimpush_dir_stack_item(apr_array_header_t *db_stack, 52362181Sdim void *dir_baton, 53362181Sdim apr_pool_t *pool) 54362181Sdim{ 55362181Sdim dir_stack_t *item = apr_pcalloc(pool, sizeof(*item)); 56362181Sdim 57362181Sdim item->dir_baton = dir_baton; 58362181Sdim item->pool = pool; 59362181Sdim APR_ARRAY_PUSH(db_stack, dir_stack_t *) = item; 60362181Sdim} 61362181Sdim 62362181Sdim 63251881Speter/* Call EDITOR's open_directory() function with the PATH argument, then 64251881Speter * add the resulting dir baton to the dir baton stack. 65251881Speter */ 66251881Speterstatic svn_error_t * 67251881Speteropen_dir(apr_array_header_t *db_stack, 68251881Speter const svn_delta_editor_t *editor, 69251881Speter const char *path, 70251881Speter apr_pool_t *pool) 71251881Speter{ 72251881Speter void *parent_db, *db; 73251881Speter dir_stack_t *item; 74251881Speter apr_pool_t *subpool; 75251881Speter 76251881Speter /* Assert that we are in a stable state. */ 77251881Speter SVN_ERR_ASSERT(db_stack && db_stack->nelts); 78251881Speter 79251881Speter /* Get the parent dir baton. */ 80251881Speter item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, void *); 81251881Speter parent_db = item->dir_baton; 82251881Speter 83251881Speter /* Call the EDITOR's open_directory function to get a new directory 84251881Speter baton. */ 85251881Speter subpool = svn_pool_create(pool); 86251881Speter SVN_ERR(editor->open_directory(path, parent_db, SVN_INVALID_REVNUM, subpool, 87251881Speter &db)); 88251881Speter 89251881Speter /* Now add the dir baton to the stack. */ 90362181Sdim push_dir_stack_item(db_stack, db, subpool); 91251881Speter 92251881Speter return SVN_NO_ERROR; 93251881Speter} 94251881Speter 95251881Speter 96251881Speter/* Pop a directory from the dir baton stack and update the stack 97251881Speter * pointer. 98251881Speter * 99251881Speter * This function calls the EDITOR's close_directory() function. 100251881Speter */ 101251881Speterstatic svn_error_t * 102251881Speterpop_stack(apr_array_header_t *db_stack, 103251881Speter const svn_delta_editor_t *editor) 104251881Speter{ 105251881Speter dir_stack_t *item; 106251881Speter 107251881Speter /* Assert that we are in a stable state. */ 108251881Speter SVN_ERR_ASSERT(db_stack && db_stack->nelts); 109251881Speter 110251881Speter /* Close the most recent directory pushed to the stack. */ 111251881Speter item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, dir_stack_t *); 112251881Speter (void) apr_array_pop(db_stack); 113251881Speter SVN_ERR(editor->close_directory(item->dir_baton, item->pool)); 114251881Speter svn_pool_destroy(item->pool); 115251881Speter 116251881Speter return SVN_NO_ERROR; 117251881Speter} 118251881Speter 119251881Speter 120251881Speter/* Count the number of path components in PATH. */ 121251881Speterstatic int 122251881Spetercount_components(const char *path) 123251881Speter{ 124251881Speter int count = 1; 125251881Speter const char *instance = path; 126251881Speter 127251881Speter if ((strlen(path) == 1) && (path[0] == '/')) 128251881Speter return 0; 129251881Speter 130251881Speter do 131251881Speter { 132251881Speter instance++; 133251881Speter instance = strchr(instance, '/'); 134251881Speter if (instance) 135251881Speter count++; 136251881Speter } 137251881Speter while (instance); 138251881Speter 139251881Speter return count; 140251881Speter} 141251881Speter 142251881Speter 143251881Speter 144251881Speter/*** Public interfaces ***/ 145251881Spetersvn_error_t * 146362181Sdimsvn_delta_path_driver3(const svn_delta_editor_t *editor, 147251881Speter void *edit_baton, 148362181Sdim const apr_array_header_t *relpaths, 149251881Speter svn_boolean_t sort_paths, 150362181Sdim svn_delta_path_driver_cb_func2_t callback_func, 151251881Speter void *callback_baton, 152251881Speter apr_pool_t *pool) 153251881Speter{ 154362181Sdim svn_delta_path_driver_state_t *state; 155362181Sdim int i; 156251881Speter apr_pool_t *subpool, *iterpool; 157251881Speter 158251881Speter /* Do nothing if there are no paths. */ 159362181Sdim if (! relpaths->nelts) 160251881Speter return SVN_NO_ERROR; 161251881Speter 162251881Speter subpool = svn_pool_create(pool); 163251881Speter iterpool = svn_pool_create(pool); 164251881Speter 165251881Speter /* sort paths if necessary */ 166362181Sdim if (sort_paths && relpaths->nelts > 1) 167251881Speter { 168362181Sdim apr_array_header_t *sorted = apr_array_copy(subpool, relpaths); 169289180Speter svn_sort__array(sorted, svn_sort_compare_paths); 170362181Sdim relpaths = sorted; 171251881Speter } 172251881Speter 173362181Sdim SVN_ERR(svn_delta_path_driver_start(&state, 174362181Sdim editor, edit_baton, 175362181Sdim callback_func, callback_baton, 176362181Sdim pool)); 177251881Speter 178251881Speter /* Now, loop over the commit items, traversing the URL tree and 179251881Speter driving the editor. */ 180362181Sdim for (i = 0; i < relpaths->nelts; i++) 181251881Speter { 182362181Sdim const char *relpath; 183251881Speter 184251881Speter /* Clear the iteration pool. */ 185251881Speter svn_pool_clear(iterpool); 186251881Speter 187251881Speter /* Get the next path. */ 188362181Sdim relpath = APR_ARRAY_IDX(relpaths, i, const char *); 189251881Speter 190362181Sdim SVN_ERR(svn_delta_path_driver_step(state, relpath, iterpool)); 191362181Sdim } 192251881Speter 193362181Sdim /* Destroy the iteration subpool. */ 194362181Sdim svn_pool_destroy(iterpool); 195251881Speter 196362181Sdim SVN_ERR(svn_delta_path_driver_finish(state, pool)); 197289180Speter 198362181Sdim return SVN_NO_ERROR; 199362181Sdim} 200251881Speter 201362181Sdimstruct svn_delta_path_driver_state_t 202362181Sdim{ 203362181Sdim const svn_delta_editor_t *editor; 204362181Sdim void *edit_baton; 205362181Sdim svn_delta_path_driver_cb_func2_t callback_func; 206362181Sdim void *callback_baton; 207362181Sdim apr_array_header_t *db_stack; 208362181Sdim const char *last_path; 209362181Sdim apr_pool_t *pool; /* at least the lifetime of the entire drive */ 210362181Sdim}; 211251881Speter 212362181Sdimsvn_error_t * 213362181Sdimsvn_delta_path_driver_start(svn_delta_path_driver_state_t **state_p, 214362181Sdim const svn_delta_editor_t *editor, 215362181Sdim void *edit_baton, 216362181Sdim svn_delta_path_driver_cb_func2_t callback_func, 217362181Sdim void *callback_baton, 218362181Sdim apr_pool_t *pool) 219362181Sdim{ 220362181Sdim svn_delta_path_driver_state_t *state = apr_pcalloc(pool, sizeof(*state)); 221251881Speter 222362181Sdim state->editor = editor; 223362181Sdim state->edit_baton = edit_baton; 224362181Sdim state->callback_func = callback_func; 225362181Sdim state->callback_baton = callback_baton; 226362181Sdim state->db_stack = apr_array_make(pool, 4, sizeof(void *)); 227362181Sdim state->last_path = NULL; 228362181Sdim state->pool = pool; 229251881Speter 230362181Sdim *state_p = state; 231362181Sdim return SVN_NO_ERROR; 232362181Sdim} 233251881Speter 234362181Sdimsvn_error_t * 235362181Sdimsvn_delta_path_driver_step(svn_delta_path_driver_state_t *state, 236362181Sdim const char *relpath, 237362181Sdim apr_pool_t *scratch_pool) 238362181Sdim{ 239362181Sdim const char *pdir; 240362181Sdim const char *common = ""; 241362181Sdim size_t common_len; 242362181Sdim apr_pool_t *subpool; 243362181Sdim dir_stack_t *item; 244362181Sdim void *parent_db, *db; 245251881Speter 246362181Sdim SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); 247362181Sdim 248362181Sdim /* If the first target path is not the root of the edit, we must first 249362181Sdim call open_root() ourselves. (If the first target path is the root of 250362181Sdim the edit, then we expect the user's callback to do so.) */ 251362181Sdim if (!state->last_path && !svn_path_is_empty(relpath)) 252362181Sdim { 253362181Sdim subpool = svn_pool_create(state->pool); 254362181Sdim SVN_ERR(state->editor->open_root(state->edit_baton, SVN_INVALID_REVNUM, 255362181Sdim subpool, &db)); 256362181Sdim push_dir_stack_item(state->db_stack, db, subpool); 257362181Sdim } 258362181Sdim 259362181Sdim /*** Step A - Find the common ancestor of the last path and the 260362181Sdim current one. For the first iteration, this is just the 261362181Sdim empty string. ***/ 262362181Sdim if (state->last_path) 263362181Sdim common = svn_relpath_get_longest_ancestor(state->last_path, relpath, 264362181Sdim scratch_pool); 265362181Sdim common_len = strlen(common); 266362181Sdim 267362181Sdim /*** Step B - Close any directories between the last path and 268362181Sdim the new common ancestor, if any need to be closed. 269362181Sdim Sometimes there is nothing to do here (like, for the first 270362181Sdim iteration, or when the last path was an ancestor of the 271362181Sdim current one). ***/ 272362181Sdim if ((state->last_path) && (strlen(state->last_path) > common_len)) 273362181Sdim { 274362181Sdim const char *rel = state->last_path + (common_len ? (common_len + 1) : 0); 275362181Sdim int count = count_components(rel); 276362181Sdim while (count--) 277251881Speter { 278362181Sdim SVN_ERR(pop_stack(state->db_stack, state->editor)); 279251881Speter } 280362181Sdim } 281362181Sdim 282362181Sdim /*** Step C - Open any directories between the common ancestor 283362181Sdim and the parent of the current path. ***/ 284362181Sdim pdir = svn_relpath_dirname(relpath, scratch_pool); 285362181Sdim 286362181Sdim if (strlen(pdir) > common_len) 287362181Sdim { 288362181Sdim const char *piece = pdir + common_len + 1; 289362181Sdim 290362181Sdim while (1) 291251881Speter { 292362181Sdim const char *rel = pdir; 293251881Speter 294362181Sdim /* Find the first separator. */ 295362181Sdim piece = strchr(piece, '/'); 296251881Speter 297362181Sdim /* Calculate REL as the portion of PDIR up to (but not 298362181Sdim including) the location to which PIECE is pointing. */ 299362181Sdim if (piece) 300362181Sdim rel = apr_pstrmemdup(scratch_pool, pdir, piece - pdir); 301362181Sdim 302362181Sdim /* Open the subdirectory. */ 303362181Sdim SVN_ERR(open_dir(state->db_stack, state->editor, rel, state->pool)); 304362181Sdim 305362181Sdim /* If we found a '/', advance our PIECE pointer to 306362181Sdim character just after that '/'. Otherwise, we're 307362181Sdim done. */ 308362181Sdim if (piece) 309362181Sdim piece++; 310362181Sdim else 311362181Sdim break; 312362181Sdim } 313251881Speter } 314251881Speter 315362181Sdim /*** Step D - Tell our caller to handle the current path. ***/ 316362181Sdim if (state->db_stack->nelts) 317362181Sdim { 318362181Sdim item = APR_ARRAY_IDX(state->db_stack, state->db_stack->nelts - 1, void *); 319362181Sdim parent_db = item->dir_baton; 320362181Sdim } 321362181Sdim else 322362181Sdim parent_db = NULL; 323362181Sdim db = NULL; /* predictable behaviour for callbacks that don't set it */ 324362181Sdim subpool = svn_pool_create(state->pool); 325362181Sdim SVN_ERR(state->callback_func(&db, 326362181Sdim state->editor, state->edit_baton, parent_db, 327362181Sdim state->callback_baton, 328362181Sdim relpath, subpool)); 329362181Sdim if (db) 330362181Sdim { 331362181Sdim push_dir_stack_item(state->db_stack, db, subpool); 332362181Sdim } 333362181Sdim else 334362181Sdim { 335362181Sdim svn_pool_destroy(subpool); 336362181Sdim } 337251881Speter 338362181Sdim /*** Step E - Save our state for the next iteration. If our 339362181Sdim caller opened or added PATH as a directory, that becomes 340362181Sdim our LAST_PATH. Otherwise, we use PATH's parent 341362181Sdim directory. ***/ 342362181Sdim state->last_path = apr_pstrdup(state->pool, db ? relpath : pdir); 343362181Sdim 344362181Sdim return SVN_NO_ERROR; 345362181Sdim} 346362181Sdim 347362181Sdimsvn_error_t * 348362181Sdimsvn_delta_path_driver_finish(svn_delta_path_driver_state_t *state, 349362181Sdim apr_pool_t *scratch_pool) 350362181Sdim{ 351251881Speter /* Close down any remaining open directory batons. */ 352362181Sdim while (state->db_stack->nelts) 353251881Speter { 354362181Sdim SVN_ERR(pop_stack(state->db_stack, state->editor)); 355251881Speter } 356251881Speter 357251881Speter return SVN_NO_ERROR; 358251881Speter} 359