Mark many merge tests as skip-against-old-server.
[svn.git] / subversion / libsvn_delta / path_driver.c
blob3306c1c6e52c4a4b64ddd67a70f5381d87d4428c
1 /*
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 * ====================================================================
20 #include <assert.h>
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"
27 #include "svn_path.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. */
38 } dir_stack_t;
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
43 * stack.
45 static svn_error_t *
46 open_dir(apr_array_header_t *db_stack,
47 const svn_delta_editor_t *editor,
48 const char *path,
49 svn_revnum_t revision,
50 apr_pool_t *pool)
52 void *parent_db, *db;
53 dir_stack_t *item;
54 apr_pool_t *subpool;
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
64 baton. */
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));
70 item->dir_baton = db;
71 item->pool = subpool;
72 APR_ARRAY_PUSH(db_stack, dir_stack_t *) = item;
74 return SVN_NO_ERROR;
78 /* Pop a directory from the dir baton stack and update the stack
79 * pointer.
81 * This function calls the EDITOR's close_directory() function.
83 static svn_error_t *
84 pop_stack(apr_array_header_t *db_stack,
85 const svn_delta_editor_t *editor)
87 dir_stack_t *item;
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);
98 return SVN_NO_ERROR;
102 /* Count the number of path components in PATH. */
103 static int
104 count_components(const char *path)
106 int count = 1;
107 const char *instance = path;
109 if ((strlen(path) == 1) && (path[0] == '/'))
110 return 0;
114 instance++;
115 instance = strchr(instance, '/');
116 if (instance)
117 count++;
119 while (instance);
121 return count;
126 /*** Public interfaces ***/
127 svn_error_t *
128 svn_delta_path_driver(const svn_delta_editor_t *editor,
129 void *edit_baton,
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,
134 apr_pool_t *pool)
136 apr_array_header_t *db_stack = apr_array_make(pool, 4, sizeof(void *));
137 const char *last_path = NULL;
138 int i = 0;
139 void *parent_db = NULL, *db = NULL;
140 const char *path;
141 apr_pool_t *subpool, *iterpool;
142 dir_stack_t *item;
144 /* Do nothing if there are no paths. */
145 if (! paths->nelts)
146 return SVN_NO_ERROR;
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()
158 ourselves. */
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));
163 last_path = path;
164 i++;
166 else
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 = "";
180 size_t common_len;
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
190 empty string. ***/
191 if (i > 0)
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
199 current one). ***/
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);
204 while (count--)
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;
217 while (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. */
226 if (piece)
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
234 done. */
235 if (piece)
236 piece++;
237 else
238 break;
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));
247 if (db)
249 item = apr_pcalloc(subpool, sizeof(*item));
250 item->dir_baton = db;
251 item->pool = subpool;
252 APR_ARRAY_PUSH(db_stack, void *) = item;
254 else
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
262 directory. ***/
264 /* NOTE: The variable LAST_PATH needs to outlive the loop. */
265 if (db)
266 last_path = path; /* lives in a pool outside our control. */
267 else
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));
280 return SVN_NO_ERROR;