1/*
2 * node_tree.c:  an editor for tracking repository deltas changes
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
26
27
28
29#include <stdio.h>
30
31#define APR_WANT_STRFUNC
32#include <apr_want.h>
33#include <apr_pools.h>
34
35#include "svn_types.h"
36#include "svn_error.h"
37#include "svn_dirent_uri.h"
38#include "svn_path.h"
39#include "svn_delta.h"
40#include "svn_fs.h"
41#include "svn_repos.h"
42#include "repos.h"
43#include "svn_private_config.h"
44#include "private/svn_fspath.h"
45
46/*** NOTE: This editor is unique in that it currently is hard-coded to
47     be anchored at the root directory of the filesystem.  This
48     affords us the ability to use the same paths for filesystem
49     locations and editor paths.  ***/
50
51
52
53/*** Node creation and assembly structures and routines. ***/
54static svn_repos_node_t *
55create_node(const char *name,
56            svn_repos_node_t *parent,
57            apr_pool_t *pool)
58{
59  svn_repos_node_t *node = apr_pcalloc(pool, sizeof(*node));
60  node->action = 'R';
61  node->kind = svn_node_unknown;
62  node->name = apr_pstrdup(pool, name);
63  node->parent = parent;
64  return node;
65}
66
67
68static svn_repos_node_t *
69create_sibling_node(svn_repos_node_t *elder,
70                    const char *name,
71                    apr_pool_t *pool)
72{
73  svn_repos_node_t *tmp_node;
74
75  /* No ELDER sibling?  That's just not gonna work out. */
76  if (! elder)
77    return NULL;
78
79  /* Run to the end of the list of siblings of ELDER. */
80  tmp_node = elder;
81  while (tmp_node->sibling)
82    tmp_node = tmp_node->sibling;
83
84  /* Create a new youngest sibling and return that. */
85  return (tmp_node->sibling = create_node(name, elder->parent, pool));
86}
87
88
89static svn_repos_node_t *
90create_child_node(svn_repos_node_t *parent,
91                  const char *name,
92                  apr_pool_t *pool)
93{
94  /* No PARENT node?  That's just not gonna work out. */
95  if (! parent)
96    return NULL;
97
98  /* If PARENT has no children, create its first one and return that. */
99  if (! parent->child)
100    return (parent->child = create_node(name, parent, pool));
101
102  /* If PARENT already has a child, create a new sibling for its first
103     child and return that. */
104  return create_sibling_node(parent->child, name, pool);
105}
106
107
108static svn_repos_node_t *
109find_child_by_name(svn_repos_node_t *parent,
110                   const char *name)
111{
112  svn_repos_node_t *tmp_node;
113
114  /* No PARENT node, or a barren PARENT?  Nothing to find. */
115  if ((! parent) || (! parent->child))
116    return NULL;
117
118  /* Look through the children for a node with a matching name. */
119  tmp_node = parent->child;
120  while (1)
121    {
122      if (! strcmp(tmp_node->name, name))
123        {
124          return tmp_node;
125        }
126      else
127        {
128          if (tmp_node->sibling)
129            tmp_node = tmp_node->sibling;
130          else
131            break;
132        }
133    }
134
135  return NULL;
136}
137
138
139static void
140find_real_base_location(const char **path_p,
141                        svn_revnum_t *rev_p,
142                        svn_repos_node_t *node,
143                        apr_pool_t *pool)
144{
145  /* If NODE is an add-with-history, then its real base location is
146     the copy source. */
147  if ((node->action == 'A')
148      && node->copyfrom_path
149      && SVN_IS_VALID_REVNUM(node->copyfrom_rev))
150    {
151      *path_p = node->copyfrom_path;
152      *rev_p = node->copyfrom_rev;
153      return;
154    }
155
156  /* Otherwise, if NODE has a parent, we'll recurse, and add NODE's
157     name to whatever the parent's real base path turns out to be (and
158     pass the base revision on through). */
159  if (node->parent)
160    {
161      const char *path;
162      svn_revnum_t rev;
163
164      find_real_base_location(&path, &rev, node->parent, pool);
165      *path_p = svn_fspath__join(path, node->name, pool);
166      *rev_p = rev;
167      return;
168    }
169
170  /* Finally, if the node has no parent, then its name is "/", and it
171     has no interesting base revision.  */
172  *path_p = "/";
173  *rev_p = SVN_INVALID_REVNUM;
174  return;
175}
176
177
178
179
180/*** Editor functions and batons. ***/
181
182struct edit_baton
183{
184  svn_fs_t *fs;
185  svn_fs_root_t *root;
186  svn_fs_root_t *base_root;
187  apr_pool_t *node_pool;
188  svn_repos_node_t *node;
189};
190
191
192struct node_baton
193{
194  struct edit_baton *edit_baton;
195  struct node_baton *parent_baton;
196  svn_repos_node_t *node;
197};
198
199
200static svn_error_t *
201delete_entry(const char *path,
202             svn_revnum_t revision,
203             void *parent_baton,
204             apr_pool_t *pool)
205{
206  struct node_baton *d = parent_baton;
207  struct edit_baton *eb = d->edit_baton;
208  svn_repos_node_t *node;
209  const char *name;
210  const char *base_path;
211  svn_revnum_t base_rev;
212  svn_fs_root_t *base_root;
213  svn_node_kind_t kind;
214
215  /* Get (or create) the change node and update it. */
216  name = svn_relpath_basename(path, pool);
217  node = find_child_by_name(d->node, name);
218  if (! node)
219    node = create_child_node(d->node, name, eb->node_pool);
220  node->action = 'D';
221
222  /* We need to look up this node's parents to see what its original
223     path in the filesystem was.  Why?  Because if this deletion
224     occurred underneath a copied path, the thing that was deleted
225     probably lived at a different location (relative to the copy
226     source). */
227  find_real_base_location(&base_path, &base_rev, node, pool);
228  if (! SVN_IS_VALID_REVNUM(base_rev))
229    {
230      /* No interesting base revision?  We'll just look for the path
231         in our base root.  */
232      base_root = eb->base_root;
233    }
234  else
235    {
236      /* Oh.  Perhaps some copy goodness happened somewhere? */
237      SVN_ERR(svn_fs_revision_root(&base_root, eb->fs, base_rev, pool));
238    }
239
240  /* Now figure out if this thing was a file or a dir. */
241  SVN_ERR(svn_fs_check_path(&kind, base_root, base_path, pool));
242  if (kind == svn_node_none)
243    return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
244                             _("'%s' not found in filesystem"), path);
245  node->kind = kind;
246
247  return SVN_NO_ERROR;
248}
249
250
251
252static svn_error_t *
253add_open_helper(const char *path,
254                char action,
255                svn_node_kind_t kind,
256                void *parent_baton,
257                const char *copyfrom_path,
258                svn_revnum_t copyfrom_rev,
259                apr_pool_t *pool,
260                void **child_baton)
261{
262  struct node_baton *pb = parent_baton;
263  struct edit_baton *eb = pb->edit_baton;
264  struct node_baton *nb = apr_pcalloc(pool, sizeof(*nb));
265
266  SVN_ERR_ASSERT(parent_baton && path);
267
268  nb->edit_baton = eb;
269  nb->parent_baton = pb;
270
271  /* Create and populate the node. */
272  nb->node = create_child_node(pb->node, svn_relpath_basename(path, NULL),
273                               eb->node_pool);
274  nb->node->kind = kind;
275  nb->node->action = action;
276  nb->node->copyfrom_rev = copyfrom_rev;
277  nb->node->copyfrom_path =
278    copyfrom_path ? apr_pstrdup(eb->node_pool, copyfrom_path) : NULL;
279
280  *child_baton = nb;
281  return SVN_NO_ERROR;
282}
283
284
285static svn_error_t *
286open_root(void *edit_baton,
287          svn_revnum_t base_revision,
288          apr_pool_t *pool,
289          void **root_baton)
290{
291  struct edit_baton *eb = edit_baton;
292  struct node_baton *d = apr_pcalloc(pool, sizeof(*d));
293
294  d->edit_baton = eb;
295  d->parent_baton = NULL;
296  d->node = (eb->node = create_node("", NULL, eb->node_pool));
297  d->node->kind = svn_node_dir;
298  d->node->action = 'R';
299  *root_baton = d;
300
301  return SVN_NO_ERROR;
302}
303
304
305static svn_error_t *
306open_directory(const char *path,
307               void *parent_baton,
308               svn_revnum_t base_revision,
309               apr_pool_t *pool,
310               void **child_baton)
311{
312  return add_open_helper(path, 'R', svn_node_dir, parent_baton,
313                         NULL, SVN_INVALID_REVNUM,
314                         pool, child_baton);
315}
316
317
318static svn_error_t *
319add_directory(const char *path,
320              void *parent_baton,
321              const char *copyfrom_path,
322              svn_revnum_t copyfrom_revision,
323              apr_pool_t *pool,
324              void **child_baton)
325{
326  return add_open_helper(path, 'A', svn_node_dir, parent_baton,
327                         copyfrom_path, copyfrom_revision,
328                         pool, child_baton);
329}
330
331
332static svn_error_t *
333open_file(const char *path,
334          void *parent_baton,
335          svn_revnum_t base_revision,
336          apr_pool_t *pool,
337          void **file_baton)
338{
339  return add_open_helper(path, 'R', svn_node_file, parent_baton,
340                         NULL, SVN_INVALID_REVNUM,
341                         pool, file_baton);
342}
343
344
345static svn_error_t *
346add_file(const char *path,
347         void *parent_baton,
348         const char *copyfrom_path,
349         svn_revnum_t copyfrom_revision,
350         apr_pool_t *pool,
351         void **file_baton)
352{
353  return add_open_helper(path, 'A', svn_node_file, parent_baton,
354                         copyfrom_path, copyfrom_revision,
355                         pool, file_baton);
356}
357
358
359static svn_error_t *
360apply_textdelta(void *file_baton,
361                const char *base_checksum,
362                apr_pool_t *pool,
363                svn_txdelta_window_handler_t *handler,
364                void **handler_baton)
365{
366  struct node_baton *fb = file_baton;
367  fb->node->text_mod = TRUE;
368  *handler = svn_delta_noop_window_handler;
369  *handler_baton = NULL;
370  return SVN_NO_ERROR;
371}
372
373
374
375static svn_error_t *
376change_node_prop(void *node_baton,
377                 const char *name,
378                 const svn_string_t *value,
379                 apr_pool_t *pool)
380{
381  struct node_baton *nb = node_baton;
382  nb->node->prop_mod = TRUE;
383  return SVN_NO_ERROR;
384}
385
386
387svn_error_t *
388svn_repos_node_editor(const svn_delta_editor_t **editor,
389                      void **edit_baton,
390                      svn_repos_t *repos,
391                      svn_fs_root_t *base_root,
392                      svn_fs_root_t *root,
393                      apr_pool_t *node_pool,
394                      apr_pool_t *pool)
395{
396  svn_delta_editor_t *my_editor;
397  struct edit_baton *my_edit_baton;
398
399  /* Set up the editor. */
400  my_editor = svn_delta_default_editor(pool);
401  my_editor->open_root           = open_root;
402  my_editor->delete_entry        = delete_entry;
403  my_editor->add_directory       = add_directory;
404  my_editor->open_directory      = open_directory;
405  my_editor->add_file            = add_file;
406  my_editor->open_file           = open_file;
407  my_editor->apply_textdelta     = apply_textdelta;
408  my_editor->change_file_prop    = change_node_prop;
409  my_editor->change_dir_prop     = change_node_prop;
410
411  /* Set up the edit baton. */
412  my_edit_baton = apr_pcalloc(pool, sizeof(*my_edit_baton));
413  my_edit_baton->node_pool = node_pool;
414  my_edit_baton->fs = repos->fs;
415  my_edit_baton->root = root;
416  my_edit_baton->base_root = base_root;
417
418  *editor = my_editor;
419  *edit_baton = my_edit_baton;
420
421  return SVN_NO_ERROR;
422}
423
424
425
426svn_repos_node_t *
427svn_repos_node_from_baton(void *edit_baton)
428{
429  struct edit_baton *eb = edit_baton;
430  return eb->node;
431}
432