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