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 
39 typedef 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  */
50 static void
push_dir_stack_item(apr_array_header_t * db_stack,void * dir_baton,apr_pool_t * pool)51 push_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  */
66 static svn_error_t *
open_dir(apr_array_header_t * db_stack,const svn_delta_editor_t * editor,const char * path,apr_pool_t * pool)67 open_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  */
101 static svn_error_t *
pop_stack(apr_array_header_t * db_stack,const svn_delta_editor_t * editor)102 pop_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. */
121 static int
count_components(const char * path)122 count_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 ***/
145 svn_error_t *
svn_delta_path_driver3(const svn_delta_editor_t * editor,void * edit_baton,const apr_array_header_t * relpaths,svn_boolean_t sort_paths,svn_delta_path_driver_cb_func2_t callback_func,void * callback_baton,apr_pool_t * pool)146 svn_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 
201 struct 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 
212 svn_error_t *
svn_delta_path_driver_start(svn_delta_path_driver_state_t ** state_p,const svn_delta_editor_t * editor,void * edit_baton,svn_delta_path_driver_cb_func2_t callback_func,void * callback_baton,apr_pool_t * pool)213 svn_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 
234 svn_error_t *
svn_delta_path_driver_step(svn_delta_path_driver_state_t * state,const char * relpath,apr_pool_t * scratch_pool)235 svn_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 
347 svn_error_t *
svn_delta_path_driver_finish(svn_delta_path_driver_state_t * state,apr_pool_t * scratch_pool)348 svn_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