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