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