2 * path_driver.c -- drive an editor across a set of paths
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include <apr_pools.h>
22 #include <apr_strings.h>
24 #include "svn_types.h"
25 #include "svn_delta.h"
26 #include "svn_pools.h"
28 #include "svn_sorts.h"
31 /*** Helper functions. ***/
33 typedef struct dir_stack_t
35 void *dir_baton
; /* the dir baton. */
36 apr_pool_t
*pool
; /* the pool associated with the dir baton. */
41 /* Call EDITOR's open_directory() function with the PATH and REVISION
42 * arguments, and then add the resulting dir baton to the dir baton
46 open_dir(apr_array_header_t
*db_stack
,
47 const svn_delta_editor_t
*editor
,
49 svn_revnum_t revision
,
56 /* Assert that we are in a stable state. */
57 assert(db_stack
&& db_stack
->nelts
);
59 /* Get the parent dir baton. */
60 item
= APR_ARRAY_IDX(db_stack
, db_stack
->nelts
- 1, void *);
61 parent_db
= item
->dir_baton
;
63 /* Call the EDITOR's open_directory function to get a new directory
65 subpool
= svn_pool_create(pool
);
66 SVN_ERR(editor
->open_directory(path
, parent_db
, revision
, subpool
, &db
));
68 /* Now add the dir baton to the stack. */
69 item
= apr_pcalloc(subpool
, sizeof(*item
));
72 APR_ARRAY_PUSH(db_stack
, dir_stack_t
*) = item
;
78 /* Pop a directory from the dir baton stack and update the stack
81 * This function calls the EDITOR's close_directory() function.
84 pop_stack(apr_array_header_t
*db_stack
,
85 const svn_delta_editor_t
*editor
)
89 /* Assert that we are in a stable state. */
90 assert(db_stack
&& db_stack
->nelts
);
92 /* Close the most recent directory pushed to the stack. */
93 item
= APR_ARRAY_IDX(db_stack
, db_stack
->nelts
- 1, dir_stack_t
*);
94 (void) apr_array_pop(db_stack
);
95 SVN_ERR(editor
->close_directory(item
->dir_baton
, item
->pool
));
96 svn_pool_destroy(item
->pool
);
102 /* Count the number of path components in PATH. */
104 count_components(const char *path
)
107 const char *instance
= path
;
109 if ((strlen(path
) == 1) && (path
[0] == '/'))
115 instance
= strchr(instance
, '/');
126 /*** Public interfaces ***/
128 svn_delta_path_driver(const svn_delta_editor_t
*editor
,
130 svn_revnum_t revision
,
131 apr_array_header_t
*paths
,
132 svn_delta_path_driver_cb_func_t callback_func
,
133 void *callback_baton
,
136 apr_array_header_t
*db_stack
= apr_array_make(pool
, 4, sizeof(void *));
137 const char *last_path
= NULL
;
139 void *parent_db
= NULL
, *db
= NULL
;
141 apr_pool_t
*subpool
, *iterpool
;
144 /* Do nothing if there are no paths. */
148 subpool
= svn_pool_create(pool
);
149 iterpool
= svn_pool_create(pool
);
150 item
= apr_pcalloc(subpool
, sizeof(*item
));
152 /* Sort the paths in a depth-first directory-ish order. */
153 qsort(paths
->elts
, paths
->nelts
, paths
->elt_size
, svn_sort_compare_paths
);
155 /* If the root of the edit is also a target path, we want to call
156 the callback function to let the user open the root directory and
157 do what needs to be done. Otherwise, we'll do the open_root()
159 path
= APR_ARRAY_IDX(paths
, 0, const char *);
160 if (svn_path_is_empty(path
))
162 SVN_ERR(callback_func(&db
, NULL
, callback_baton
, path
, subpool
));
168 SVN_ERR(editor
->open_root(edit_baton
, revision
, subpool
, &db
));
170 item
->pool
= subpool
;
171 item
->dir_baton
= db
;
172 APR_ARRAY_PUSH(db_stack
, void *) = item
;
174 /* Now, loop over the commit items, traversing the URL tree and
175 driving the editor. */
176 for (; i
< paths
->nelts
; i
++)
178 const char *pdir
, *bname
;
179 const char *common
= "";
182 /* Clear the iteration pool. */
183 svn_pool_clear(iterpool
);
185 /* Get the next path. */
186 path
= APR_ARRAY_IDX(paths
, i
, const char *);
188 /*** Step A - Find the common ancestor of the last path and the
189 current one. For the first iteration, this is just the
192 common
= svn_path_get_longest_ancestor(last_path
, path
, iterpool
);
193 common_len
= strlen(common
);
195 /*** Step B - Close any directories between the last path and
196 the new common ancestor, if any need to be closed.
197 Sometimes there is nothing to do here (like, for the first
198 iteration, or when the last path was an ancestor of the
200 if ((i
> 0) && (strlen(last_path
) > common_len
))
202 const char *rel
= last_path
+ (common_len
? (common_len
+ 1) : 0);
203 int count
= count_components(rel
);
206 SVN_ERR(pop_stack(db_stack
, editor
));
210 /*** Step C - Open any directories between the common ancestor
211 and the parent of the current path. ***/
212 svn_path_split(path
, &pdir
, &bname
, iterpool
);
213 if (strlen(pdir
) > common_len
)
215 const char *piece
= pdir
+ common_len
+ 1;
219 const char *rel
= pdir
;
221 /* Find the first separator. */
222 piece
= strchr(piece
, '/');
224 /* Calculate REL as the portion of PDIR up to (but not
225 including) the location to which PIECE is pointing. */
227 rel
= apr_pstrmemdup(iterpool
, pdir
, piece
- pdir
);
229 /* Open the subdirectory. */
230 SVN_ERR(open_dir(db_stack
, editor
, rel
, revision
, pool
));
232 /* If we found a '/', advance our PIECE pointer to
233 character just after that '/'. Otherwise, we're
242 /*** Step D - Tell our caller to handle the current path. ***/
243 item
= APR_ARRAY_IDX(db_stack
, db_stack
->nelts
- 1, void *);
244 parent_db
= item
->dir_baton
;
245 subpool
= svn_pool_create(pool
);
246 SVN_ERR(callback_func(&db
, parent_db
, callback_baton
, path
, subpool
));
249 item
= apr_pcalloc(subpool
, sizeof(*item
));
250 item
->dir_baton
= db
;
251 item
->pool
= subpool
;
252 APR_ARRAY_PUSH(db_stack
, void *) = item
;
256 svn_pool_destroy(subpool
);
259 /*** Step E - Save our state for the next iteration. If our
260 caller opened or added PATH as a directory, that becomes
261 our LAST_PATH. Otherwise, we use PATH's parent
264 /* NOTE: The variable LAST_PATH needs to outlive the loop. */
266 last_path
= path
; /* lives in a pool outside our control. */
268 last_path
= apr_pstrdup(pool
, pdir
); /* duping into POOL. */
271 /* Destroy the iteration subpool. */
272 svn_pool_destroy(iterpool
);
274 /* Close down any remaining open directory batons. */
275 while (db_stack
->nelts
)
277 SVN_ERR(pop_stack(db_stack
, editor
));