1 /* dag.c : DAG-like interface filesystem, private to libsvn_fs
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22 
23 #include <string.h>
24 
25 #include "svn_path.h"
26 #include "svn_time.h"
27 #include "svn_error.h"
28 #include "svn_fs.h"
29 #include "svn_hash.h"
30 #include "svn_props.h"
31 #include "svn_pools.h"
32 
33 #include "dag.h"
34 #include "err.h"
35 #include "fs.h"
36 #include "key-gen.h"
37 #include "node-rev.h"
38 #include "trail.h"
39 #include "reps-strings.h"
40 #include "revs-txns.h"
41 #include "id.h"
42 
43 #include "util/fs_skels.h"
44 
45 #include "bdb/txn-table.h"
46 #include "bdb/rev-table.h"
47 #include "bdb/nodes-table.h"
48 #include "bdb/copies-table.h"
49 #include "bdb/reps-table.h"
50 #include "bdb/strings-table.h"
51 #include "bdb/checksum-reps-table.h"
52 #include "bdb/changes-table.h"
53 #include "bdb/node-origins-table.h"
54 
55 #include "private/svn_skel.h"
56 #include "private/svn_fs_util.h"
57 #include "private/svn_fspath.h"
58 #include "../libsvn_fs/fs-loader.h"
59 
60 #include "svn_private_config.h"
61 
62 
63 /* Initializing a filesystem.  */
64 
65 struct dag_node_t
66 {
67   /*** NOTE: Keeping in-memory representations of disk data that can
68        be changed by other accessors is a nasty business.  Such
69        representations are basically a cache with some pretty complex
70        invalidation rules.  For example, the "node revision"
71        associated with a DAG node ID can look completely different to
72        a process that has modified that information as part of a
73        Berkeley DB transaction than it does to some other process.
74        That said, there are some aspects of a "node revision" which
75        never change, like its 'id' or 'kind'.  Our best bet is to
76        limit ourselves to exposing outside of this interface only
77        those immutable aspects of a DAG node representation.  ***/
78 
79   /* The filesystem this dag node came from. */
80   svn_fs_t *fs;
81 
82   /* The node revision ID for this dag node. */
83   svn_fs_id_t *id;
84 
85   /* The node's type (file, dir, etc.) */
86   svn_node_kind_t kind;
87 
88   /* the path at which this node was created. */
89   const char *created_path;
90 };
91 
92 
93 
94 /* Trivial helper/accessor functions. */
svn_fs_base__dag_node_kind(dag_node_t * node)95 svn_node_kind_t svn_fs_base__dag_node_kind(dag_node_t *node)
96 {
97   return node->kind;
98 }
99 
100 
101 const svn_fs_id_t *
svn_fs_base__dag_get_id(dag_node_t * node)102 svn_fs_base__dag_get_id(dag_node_t *node)
103 {
104   return node->id;
105 }
106 
107 
108 const char *
svn_fs_base__dag_get_created_path(dag_node_t * node)109 svn_fs_base__dag_get_created_path(dag_node_t *node)
110 {
111   return node->created_path;
112 }
113 
114 
115 svn_fs_t *
svn_fs_base__dag_get_fs(dag_node_t * node)116 svn_fs_base__dag_get_fs(dag_node_t *node)
117 {
118   return node->fs;
119 }
120 
121 
svn_fs_base__dag_check_mutable(dag_node_t * node,const char * txn_id)122 svn_boolean_t svn_fs_base__dag_check_mutable(dag_node_t *node,
123                                              const char *txn_id)
124 {
125   return (strcmp(svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)),
126                  txn_id) == 0);
127 }
128 
129 
130 svn_error_t *
svn_fs_base__dag_get_node(dag_node_t ** node,svn_fs_t * fs,const svn_fs_id_t * id,trail_t * trail,apr_pool_t * pool)131 svn_fs_base__dag_get_node(dag_node_t **node,
132                           svn_fs_t *fs,
133                           const svn_fs_id_t *id,
134                           trail_t *trail,
135                           apr_pool_t *pool)
136 {
137   dag_node_t *new_node;
138   node_revision_t *noderev;
139 
140   /* Construct the node. */
141   new_node = apr_pcalloc(pool, sizeof(*new_node));
142   new_node->fs = fs;
143   new_node->id = svn_fs_base__id_copy(id, pool);
144 
145   /* Grab the contents so we can cache some of the immutable parts of it. */
146   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool));
147 
148   /* Initialize the KIND and CREATED_PATH attributes */
149   new_node->kind = noderev->kind;
150   new_node->created_path = noderev->created_path;
151 
152   /* Return a fresh new node */
153   *node = new_node;
154   return SVN_NO_ERROR;
155 }
156 
157 
158 svn_error_t *
svn_fs_base__dag_get_revision(svn_revnum_t * rev,dag_node_t * node,trail_t * trail,apr_pool_t * pool)159 svn_fs_base__dag_get_revision(svn_revnum_t *rev,
160                               dag_node_t *node,
161                               trail_t *trail,
162                               apr_pool_t *pool)
163 {
164   /* Use the txn ID from the NODE's id to look up the transaction and
165      get its revision number.  */
166   return svn_fs_base__txn_get_revision
167     (rev, svn_fs_base__dag_get_fs(node),
168      svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)), trail, pool);
169 }
170 
171 
172 svn_error_t *
svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t ** id_p,dag_node_t * node,trail_t * trail,apr_pool_t * pool)173 svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t **id_p,
174                                     dag_node_t *node,
175                                     trail_t *trail,
176                                     apr_pool_t *pool)
177 {
178   node_revision_t *noderev;
179 
180   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
181                                         trail, pool));
182   *id_p = noderev->predecessor_id;
183   return SVN_NO_ERROR;
184 }
185 
186 
187 svn_error_t *
svn_fs_base__dag_get_predecessor_count(int * count,dag_node_t * node,trail_t * trail,apr_pool_t * pool)188 svn_fs_base__dag_get_predecessor_count(int *count,
189                                        dag_node_t *node,
190                                        trail_t *trail,
191                                        apr_pool_t *pool)
192 {
193   node_revision_t *noderev;
194 
195   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
196                                         trail, pool));
197   *count = noderev->predecessor_count;
198   return SVN_NO_ERROR;
199 }
200 
201 
202 /* Trail body for svn_fs_base__dag_init_fs. */
203 static svn_error_t *
txn_body_dag_init_fs(void * baton,trail_t * trail)204 txn_body_dag_init_fs(void *baton,
205                      trail_t *trail)
206 {
207   node_revision_t noderev;
208   revision_t revision;
209   svn_revnum_t rev = SVN_INVALID_REVNUM;
210   svn_fs_t *fs = trail->fs;
211   svn_string_t date;
212   const char *txn_id;
213   const char *copy_id;
214   svn_fs_id_t *root_id = svn_fs_base__id_create("0", "0", "0", trail->pool);
215 
216   /* Create empty root directory with node revision 0.0.0. */
217   memset(&noderev, 0, sizeof(noderev));
218   noderev.kind = svn_node_dir;
219   noderev.created_path = "/";
220   SVN_ERR(svn_fs_bdb__put_node_revision(fs, root_id, &noderev,
221                                         trail, trail->pool));
222 
223   /* Create a new transaction (better have an id of "0") */
224   SVN_ERR(svn_fs_bdb__create_txn(&txn_id, fs, root_id, trail, trail->pool));
225   if (strcmp(txn_id, "0"))
226     return svn_error_createf
227       (SVN_ERR_FS_CORRUPT, 0,
228        _("Corrupt DB: initial transaction id not '0' in filesystem '%s'"),
229        fs->path);
230 
231   /* Create a default copy (better have an id of "0") */
232   SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, trail->pool));
233   if (strcmp(copy_id, "0"))
234     return svn_error_createf
235       (SVN_ERR_FS_CORRUPT, 0,
236        _("Corrupt DB: initial copy id not '0' in filesystem '%s'"), fs->path);
237   SVN_ERR(svn_fs_bdb__create_copy(fs, copy_id, NULL, NULL, root_id,
238                                   copy_kind_real, trail, trail->pool));
239 
240   /* Link it into filesystem revision 0. */
241   revision.txn_id = txn_id;
242   SVN_ERR(svn_fs_bdb__put_rev(&rev, fs, &revision, trail, trail->pool));
243   if (rev != 0)
244     return svn_error_createf(SVN_ERR_FS_CORRUPT, 0,
245                              _("Corrupt DB: initial revision number "
246                                "is not '0' in filesystem '%s'"), fs->path);
247 
248   /* Promote our transaction to a "committed" transaction. */
249   SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, rev,
250                                           trail, trail->pool));
251 
252   /* Set a date on revision 0. */
253   date.data = svn_time_to_cstring(apr_time_now(), trail->pool);
254   date.len = strlen(date.data);
255   return svn_fs_base__set_rev_prop(fs, 0, SVN_PROP_REVISION_DATE, NULL, &date,
256                                    trail, trail->pool);
257 }
258 
259 
260 svn_error_t *
svn_fs_base__dag_init_fs(svn_fs_t * fs)261 svn_fs_base__dag_init_fs(svn_fs_t *fs)
262 {
263   return svn_fs_base__retry_txn(fs, txn_body_dag_init_fs, NULL,
264                                 TRUE, fs->pool);
265 }
266 
267 
268 
269 /*** Directory node functions ***/
270 
271 /* Some of these are helpers for functions outside this section. */
272 
273 /* Given directory NODEREV in FS, set *ENTRIES_P to its entries list
274    hash, as part of TRAIL, or to NULL if NODEREV has no entries.  The
275    entries list will be allocated in POOL, and the entries in that
276    list will not have interesting value in their 'kind' fields.  If
277    NODEREV is not a directory, return the error SVN_ERR_FS_NOT_DIRECTORY. */
278 static svn_error_t *
get_dir_entries(apr_hash_t ** entries_p,svn_fs_t * fs,node_revision_t * noderev,trail_t * trail,apr_pool_t * pool)279 get_dir_entries(apr_hash_t **entries_p,
280                 svn_fs_t *fs,
281                 node_revision_t *noderev,
282                 trail_t *trail,
283                 apr_pool_t *pool)
284 {
285   apr_hash_t *entries = NULL;
286   apr_hash_index_t *hi;
287   svn_string_t entries_raw;
288   svn_skel_t *entries_skel;
289 
290   /* Error if this is not a directory. */
291   if (noderev->kind != svn_node_dir)
292     return svn_error_create
293       (SVN_ERR_FS_NOT_DIRECTORY, NULL,
294        _("Attempted to get entries of a non-directory node"));
295 
296   /* If there's a DATA-KEY, there might be entries to fetch. */
297   if (noderev->data_key)
298     {
299       /* Now we have a rep, follow through to get the entries. */
300       SVN_ERR(svn_fs_base__rep_contents(&entries_raw, fs, noderev->data_key,
301                                         trail, pool));
302       entries_skel = svn_skel__parse(entries_raw.data, entries_raw.len, pool);
303 
304       /* Were there entries?  Make a hash from them. */
305       if (entries_skel)
306         SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
307                                                 pool));
308     }
309 
310   /* No hash?  No problem.  */
311   *entries_p = NULL;
312   if (! entries)
313     return SVN_NO_ERROR;
314 
315   /* Else, convert the hash from a name->id mapping to a name->dirent one.  */
316   *entries_p = apr_hash_make(pool);
317   for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
318     {
319       const void *key;
320       apr_ssize_t klen;
321       void *val;
322       svn_fs_dirent_t *dirent = apr_palloc(pool, sizeof(*dirent));
323 
324       /* KEY will be the entry name in ancestor, VAL the id.  */
325       apr_hash_this(hi, &key, &klen, &val);
326       dirent->name = key;
327       dirent->id = val;
328       dirent->kind = svn_node_unknown;
329       apr_hash_set(*entries_p, key, klen, dirent);
330     }
331 
332   /* Return our findings. */
333   return SVN_NO_ERROR;
334 }
335 
336 
337 /* Set *ID_P to the node-id for entry NAME in PARENT, as part of
338    TRAIL.  If no such entry, set *ID_P to NULL but do not error.  The
339    entry is allocated in POOL or in the same pool as PARENT;
340    the caller should copy if it cares.  */
341 static svn_error_t *
dir_entry_id_from_node(const svn_fs_id_t ** id_p,dag_node_t * parent,const char * name,trail_t * trail,apr_pool_t * pool)342 dir_entry_id_from_node(const svn_fs_id_t **id_p,
343                        dag_node_t *parent,
344                        const char *name,
345                        trail_t *trail,
346                        apr_pool_t *pool)
347 {
348   apr_hash_t *entries;
349   svn_fs_dirent_t *dirent;
350 
351   SVN_ERR(svn_fs_base__dag_dir_entries(&entries, parent, trail, pool));
352   if (entries)
353     dirent = svn_hash_gets(entries, name);
354   else
355     dirent = NULL;
356 
357   *id_p = dirent ? dirent->id : NULL;
358   return SVN_NO_ERROR;
359 }
360 
361 
362 /* Add or set in PARENT a directory entry NAME pointing to ID.
363    Allocations are done in TRAIL.
364 
365    Assumptions:
366    - PARENT is a mutable directory.
367    - ID does not refer to an ancestor of parent
368    - NAME is a single path component
369 */
370 static svn_error_t *
set_entry(dag_node_t * parent,const char * name,const svn_fs_id_t * id,const char * txn_id,trail_t * trail,apr_pool_t * pool)371 set_entry(dag_node_t *parent,
372           const char *name,
373           const svn_fs_id_t *id,
374           const char *txn_id,
375           trail_t *trail,
376           apr_pool_t *pool)
377 {
378   node_revision_t *parent_noderev;
379   const char *rep_key, *mutable_rep_key;
380   apr_hash_t *entries = NULL;
381   svn_stream_t *wstream;
382   apr_size_t len;
383   svn_string_t raw_entries;
384   svn_stringbuf_t *raw_entries_buf;
385   svn_skel_t *entries_skel;
386   svn_fs_t *fs = svn_fs_base__dag_get_fs(parent);
387 
388   /* Get the parent's node-revision. */
389   SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
390                                         trail, pool));
391   rep_key = parent_noderev->data_key;
392   SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
393                                        fs, txn_id, trail, pool));
394 
395   /* If the parent node already pointed at a mutable representation,
396      we don't need to do anything.  But if it didn't, either because
397      the parent didn't refer to any rep yet or because it referred to
398      an immutable one, we must make the parent refer to the mutable
399      rep we just created. */
400   if (! svn_fs_base__same_keys(rep_key, mutable_rep_key))
401     {
402       parent_noderev->data_key = mutable_rep_key;
403       SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
404                                             trail, pool));
405     }
406 
407   /* If the new representation inherited nothing, start a new entries
408      list for it.  Else, go read its existing entries list. */
409   if (rep_key)
410     {
411       SVN_ERR(svn_fs_base__rep_contents(&raw_entries, fs, rep_key,
412                                         trail, pool));
413       entries_skel = svn_skel__parse(raw_entries.data, raw_entries.len, pool);
414       if (entries_skel)
415         SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
416                                                 pool));
417     }
418 
419   /* If we still have no ENTRIES hash, make one here.  */
420   if (! entries)
421     entries = apr_hash_make(pool);
422 
423   /* Now, add our new entry to the entries list. */
424   svn_hash_sets(entries, name, id);
425 
426   /* Finally, replace the old entries list with the new one. */
427   SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries,
428                                             pool));
429   raw_entries_buf = svn_skel__unparse(entries_skel, pool);
430   SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs,
431                                                  mutable_rep_key, txn_id,
432                                                  TRUE, trail, pool));
433   len = raw_entries_buf->len;
434   SVN_ERR(svn_stream_write(wstream, raw_entries_buf->data, &len));
435   return svn_stream_close(wstream);
436 }
437 
438 
439 /* Make a new entry named NAME in PARENT, as part of TRAIL.  If IS_DIR
440    is true, then the node revision the new entry points to will be a
441    directory, else it will be a file.  The new node will be allocated
442    in POOL.  PARENT must be mutable, and must not have an entry
443    named NAME.  */
444 static svn_error_t *
make_entry(dag_node_t ** child_p,dag_node_t * parent,const char * parent_path,const char * name,svn_boolean_t is_dir,const char * txn_id,trail_t * trail,apr_pool_t * pool)445 make_entry(dag_node_t **child_p,
446            dag_node_t *parent,
447            const char *parent_path,
448            const char *name,
449            svn_boolean_t is_dir,
450            const char *txn_id,
451            trail_t *trail,
452            apr_pool_t *pool)
453 {
454   const svn_fs_id_t *new_node_id;
455   node_revision_t new_noderev;
456 
457   /* Make sure that NAME is a single path component. */
458   if (! svn_path_is_single_path_component(name))
459     return svn_error_createf
460       (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
461        _("Attempted to create a node with an illegal name '%s'"), name);
462 
463   /* Make sure that parent is a directory */
464   if (parent->kind != svn_node_dir)
465     return svn_error_create
466       (SVN_ERR_FS_NOT_DIRECTORY, NULL,
467        _("Attempted to create entry in non-directory parent"));
468 
469   /* Check that the parent is mutable. */
470   if (! svn_fs_base__dag_check_mutable(parent, txn_id))
471     return svn_error_createf
472       (SVN_ERR_FS_NOT_MUTABLE, NULL,
473        _("Attempted to clone child of non-mutable node"));
474 
475   /* Check that parent does not already have an entry named NAME. */
476   SVN_ERR(dir_entry_id_from_node(&new_node_id, parent, name, trail, pool));
477   if (new_node_id)
478     return svn_error_createf
479       (SVN_ERR_FS_ALREADY_EXISTS, NULL,
480        _("Attempted to create entry that already exists"));
481 
482   /* Create the new node's NODE-REVISION */
483   memset(&new_noderev, 0, sizeof(new_noderev));
484   new_noderev.kind = is_dir ? svn_node_dir : svn_node_file;
485   new_noderev.created_path = svn_fspath__join(parent_path, name, pool);
486   SVN_ERR(svn_fs_base__create_node
487           (&new_node_id, svn_fs_base__dag_get_fs(parent), &new_noderev,
488            svn_fs_base__id_copy_id(svn_fs_base__dag_get_id(parent)),
489            txn_id, trail, pool));
490 
491   /* Create a new dag_node_t for our new node */
492   SVN_ERR(svn_fs_base__dag_get_node(child_p,
493                                     svn_fs_base__dag_get_fs(parent),
494                                     new_node_id, trail, pool));
495 
496   /* We can safely call set_entry because we already know that
497      PARENT is mutable, and we just created CHILD, so we know it has
498      no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */
499   return set_entry(parent, name, svn_fs_base__dag_get_id(*child_p),
500                    txn_id, trail, pool);
501 }
502 
503 
504 svn_error_t *
svn_fs_base__dag_dir_entries(apr_hash_t ** entries,dag_node_t * node,trail_t * trail,apr_pool_t * pool)505 svn_fs_base__dag_dir_entries(apr_hash_t **entries,
506                              dag_node_t *node,
507                              trail_t *trail,
508                              apr_pool_t *pool)
509 {
510   node_revision_t *noderev;
511   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
512                                         trail, pool));
513   return get_dir_entries(entries, node->fs, noderev, trail, pool);
514 }
515 
516 
517 svn_error_t *
svn_fs_base__dag_set_entry(dag_node_t * node,const char * entry_name,const svn_fs_id_t * id,const char * txn_id,trail_t * trail,apr_pool_t * pool)518 svn_fs_base__dag_set_entry(dag_node_t *node,
519                            const char *entry_name,
520                            const svn_fs_id_t *id,
521                            const char *txn_id,
522                            trail_t *trail,
523                            apr_pool_t *pool)
524 {
525   /* Check it's a directory. */
526   if (node->kind != svn_node_dir)
527     return svn_error_create
528       (SVN_ERR_FS_NOT_DIRECTORY, NULL,
529        _("Attempted to set entry in non-directory node"));
530 
531   /* Check it's mutable. */
532   if (! svn_fs_base__dag_check_mutable(node, txn_id))
533     return svn_error_create
534       (SVN_ERR_FS_NOT_MUTABLE, NULL,
535        _("Attempted to set entry in immutable node"));
536 
537   return set_entry(node, entry_name, id, txn_id, trail, pool);
538 }
539 
540 
541 
542 /*** Proplists. ***/
543 
544 svn_error_t *
svn_fs_base__dag_get_proplist(apr_hash_t ** proplist_p,dag_node_t * node,trail_t * trail,apr_pool_t * pool)545 svn_fs_base__dag_get_proplist(apr_hash_t **proplist_p,
546                               dag_node_t *node,
547                               trail_t *trail,
548                               apr_pool_t *pool)
549 {
550   node_revision_t *noderev;
551   apr_hash_t *proplist = NULL;
552   svn_string_t raw_proplist;
553   svn_skel_t *proplist_skel;
554 
555   /* Go get a fresh NODE-REVISION for this node. */
556   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
557                                         trail, pool));
558 
559   /* Get property key (returning early if there isn't one) . */
560   if (! noderev->prop_key)
561     {
562       *proplist_p = NULL;
563       return SVN_NO_ERROR;
564     }
565 
566   /* Get the string associated with the property rep, parsing it as a
567      skel, and then attempt to parse *that* into a property hash.  */
568   SVN_ERR(svn_fs_base__rep_contents(&raw_proplist,
569                                     svn_fs_base__dag_get_fs(node),
570                                     noderev->prop_key, trail, pool));
571   proplist_skel = svn_skel__parse(raw_proplist.data, raw_proplist.len, pool);
572   if (proplist_skel)
573     SVN_ERR(svn_skel__parse_proplist(&proplist, proplist_skel, pool));
574 
575   *proplist_p = proplist;
576   return SVN_NO_ERROR;
577 }
578 
579 
580 svn_error_t *
svn_fs_base__dag_set_proplist(dag_node_t * node,const apr_hash_t * proplist,const char * txn_id,trail_t * trail,apr_pool_t * pool)581 svn_fs_base__dag_set_proplist(dag_node_t *node,
582                               const apr_hash_t *proplist,
583                               const char *txn_id,
584                               trail_t *trail,
585                               apr_pool_t *pool)
586 {
587   node_revision_t *noderev;
588   const char *rep_key, *mutable_rep_key;
589   svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
590   svn_stream_t *wstream;
591   apr_size_t len;
592   svn_skel_t *proplist_skel;
593   svn_stringbuf_t *raw_proplist_buf;
594   base_fs_data_t *bfd = fs->fsap_data;
595 
596   /* Sanity check: this node better be mutable! */
597   if (! svn_fs_base__dag_check_mutable(node, txn_id))
598     {
599       svn_string_t *idstr = svn_fs_base__id_unparse(node->id, pool);
600       return svn_error_createf
601         (SVN_ERR_FS_NOT_MUTABLE, NULL,
602          _("Can't set proplist on *immutable* node-revision %s"),
603          idstr->data);
604     }
605 
606   /* Go get a fresh NODE-REVISION for this node. */
607   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, node->id,
608                                         trail, pool));
609   rep_key = noderev->prop_key;
610 
611   /* Flatten the proplist into a string. */
612   SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, proplist, pool));
613   raw_proplist_buf = svn_skel__unparse(proplist_skel, pool);
614 
615   /* If this repository supports representation sharing, and the
616      resulting property list is exactly the same as another string in
617      the database, just use the previously existing string and get
618      outta here. */
619   if (bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
620     {
621       svn_error_t *err;
622       const char *dup_rep_key;
623       svn_checksum_t *checksum;
624 
625       SVN_ERR(svn_checksum(&checksum, svn_checksum_sha1, raw_proplist_buf->data,
626                            raw_proplist_buf->len, pool));
627 
628       err = svn_fs_bdb__get_checksum_rep(&dup_rep_key, fs, checksum,
629                                          trail, pool);
630       if (! err)
631         {
632           if (noderev->prop_key)
633             SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key,
634                                                        txn_id, trail, pool));
635           noderev->prop_key = dup_rep_key;
636           return svn_fs_bdb__put_node_revision(fs, node->id, noderev,
637                                                trail, pool);
638         }
639       else if (err)
640         {
641           if (err->apr_err != SVN_ERR_FS_NO_SUCH_CHECKSUM_REP)
642             return svn_error_trace(err);
643 
644           svn_error_clear(err);
645           err = SVN_NO_ERROR;
646         }
647     }
648 
649   /* Get a mutable version of this rep (updating the node revision if
650      this isn't a NOOP)  */
651   SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
652                                        fs, txn_id, trail, pool));
653   if (! svn_fs_base__same_keys(mutable_rep_key, rep_key))
654     {
655       noderev->prop_key = mutable_rep_key;
656       SVN_ERR(svn_fs_bdb__put_node_revision(fs, node->id, noderev,
657                                             trail, pool));
658     }
659 
660   /* Replace the old property list with the new one. */
661   SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs,
662                                                  mutable_rep_key, txn_id,
663                                                  TRUE, trail, pool));
664   len = raw_proplist_buf->len;
665   SVN_ERR(svn_stream_write(wstream, raw_proplist_buf->data, &len));
666   SVN_ERR(svn_stream_close(wstream));
667 
668   return SVN_NO_ERROR;
669 }
670 
671 
672 
673 /*** Roots. ***/
674 
675 svn_error_t *
svn_fs_base__dag_revision_root(dag_node_t ** node_p,svn_fs_t * fs,svn_revnum_t rev,trail_t * trail,apr_pool_t * pool)676 svn_fs_base__dag_revision_root(dag_node_t **node_p,
677                                svn_fs_t *fs,
678                                svn_revnum_t rev,
679                                trail_t *trail,
680                                apr_pool_t *pool)
681 {
682   const svn_fs_id_t *root_id;
683 
684   SVN_ERR(svn_fs_base__rev_get_root(&root_id, fs, rev, trail, pool));
685   return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool);
686 }
687 
688 
689 svn_error_t *
svn_fs_base__dag_txn_root(dag_node_t ** node_p,svn_fs_t * fs,const char * txn_id,trail_t * trail,apr_pool_t * pool)690 svn_fs_base__dag_txn_root(dag_node_t **node_p,
691                           svn_fs_t *fs,
692                           const char *txn_id,
693                           trail_t *trail,
694                           apr_pool_t *pool)
695 {
696   const svn_fs_id_t *root_id, *ignored;
697 
698   SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &ignored, fs, txn_id,
699                                    trail, pool));
700   return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool);
701 }
702 
703 
704 svn_error_t *
svn_fs_base__dag_txn_base_root(dag_node_t ** node_p,svn_fs_t * fs,const char * txn_id,trail_t * trail,apr_pool_t * pool)705 svn_fs_base__dag_txn_base_root(dag_node_t **node_p,
706                                svn_fs_t *fs,
707                                const char *txn_id,
708                                trail_t *trail,
709                                apr_pool_t *pool)
710 {
711   const svn_fs_id_t *base_root_id, *ignored;
712 
713   SVN_ERR(svn_fs_base__get_txn_ids(&ignored, &base_root_id, fs, txn_id,
714                                    trail, pool));
715   return svn_fs_base__dag_get_node(node_p, fs, base_root_id, trail, pool);
716 }
717 
718 
719 svn_error_t *
svn_fs_base__dag_clone_child(dag_node_t ** child_p,dag_node_t * parent,const char * parent_path,const char * name,const char * copy_id,const char * txn_id,trail_t * trail,apr_pool_t * pool)720 svn_fs_base__dag_clone_child(dag_node_t **child_p,
721                              dag_node_t *parent,
722                              const char *parent_path,
723                              const char *name,
724                              const char *copy_id,
725                              const char *txn_id,
726                              trail_t *trail,
727                              apr_pool_t *pool)
728 {
729   dag_node_t *cur_entry; /* parent's current entry named NAME */
730   const svn_fs_id_t *new_node_id; /* node id we'll put into NEW_NODE */
731   svn_fs_t *fs = svn_fs_base__dag_get_fs(parent);
732 
733   /* First check that the parent is mutable. */
734   if (! svn_fs_base__dag_check_mutable(parent, txn_id))
735     return svn_error_createf
736       (SVN_ERR_FS_NOT_MUTABLE, NULL,
737        _("Attempted to clone child of non-mutable node"));
738 
739   /* Make sure that NAME is a single path component. */
740   if (! svn_path_is_single_path_component(name))
741     return svn_error_createf
742       (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
743        _("Attempted to make a child clone with an illegal name '%s'"), name);
744 
745   /* Find the node named NAME in PARENT's entries list if it exists. */
746   SVN_ERR(svn_fs_base__dag_open(&cur_entry, parent, name, trail, pool));
747 
748   /* Check for mutability in the node we found.  If it's mutable, we
749      don't need to clone it. */
750   if (svn_fs_base__dag_check_mutable(cur_entry, txn_id))
751     {
752       /* This has already been cloned */
753       new_node_id = cur_entry->id;
754     }
755   else
756     {
757       node_revision_t *noderev;
758 
759       /* Go get a fresh NODE-REVISION for current child node. */
760       SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, cur_entry->id,
761                                             trail, pool));
762 
763       /* Do the clone thingy here. */
764       noderev->predecessor_id = cur_entry->id;
765       if (noderev->predecessor_count != -1)
766         noderev->predecessor_count++;
767       noderev->created_path = svn_fspath__join(parent_path, name, pool);
768       SVN_ERR(svn_fs_base__create_successor(&new_node_id, fs, cur_entry->id,
769                                             noderev, copy_id, txn_id,
770                                             trail, pool));
771 
772       /* Replace the ID in the parent's ENTRY list with the ID which
773          refers to the mutable clone of this child. */
774       SVN_ERR(set_entry(parent, name, new_node_id, txn_id, trail, pool));
775     }
776 
777   /* Initialize the youngster. */
778   return svn_fs_base__dag_get_node(child_p, fs, new_node_id, trail, pool);
779 }
780 
781 
782 
783 svn_error_t *
svn_fs_base__dag_clone_root(dag_node_t ** root_p,svn_fs_t * fs,const char * txn_id,trail_t * trail,apr_pool_t * pool)784 svn_fs_base__dag_clone_root(dag_node_t **root_p,
785                             svn_fs_t *fs,
786                             const char *txn_id,
787                             trail_t *trail,
788                             apr_pool_t *pool)
789 {
790   const svn_fs_id_t *base_root_id, *root_id;
791   node_revision_t *noderev;
792 
793   /* Get the node ID's of the root directories of the transaction and
794      its base revision.  */
795   SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &base_root_id, fs, txn_id,
796                                    trail, pool));
797 
798   /* Oh, give me a clone...
799      (If they're the same, we haven't cloned the transaction's root
800      directory yet.)  */
801   if (svn_fs_base__id_eq(root_id, base_root_id))
802     {
803       const char *base_copy_id = svn_fs_base__id_copy_id(base_root_id);
804 
805       /* Of my own flesh and bone...
806          (Get the NODE-REVISION for the base node, and then write
807          it back out as the clone.) */
808       SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, base_root_id,
809                                             trail, pool));
810 
811       /* With its Y-chromosome changed to X...
812          (Store it with an updated predecessor count.) */
813       /* ### TODO: Does it even makes sense to have a different copy id for
814          the root node?  That is, does this function need a copy_id
815          passed in?  */
816       noderev->predecessor_id = svn_fs_base__id_copy(base_root_id, pool);
817       if (noderev->predecessor_count != -1)
818         noderev->predecessor_count++;
819       SVN_ERR(svn_fs_base__create_successor(&root_id, fs, base_root_id,
820                                             noderev, base_copy_id,
821                                             txn_id, trail, pool));
822 
823       /* ... And when it is grown
824        *      Then my own little clone
825        *        Will be of the opposite sex!
826        */
827       SVN_ERR(svn_fs_base__set_txn_root(fs, txn_id, root_id, trail, pool));
828     }
829 
830   /*
831    * (Sung to the tune of "Home, Home on the Range", with thanks to
832    * Randall Garrett and Isaac Asimov.)
833    */
834 
835   /* One way or another, root_id now identifies a cloned root node. */
836   return svn_fs_base__dag_get_node(root_p, fs, root_id, trail, pool);
837 }
838 
839 
840 svn_error_t *
svn_fs_base__dag_delete(dag_node_t * parent,const char * name,const char * txn_id,trail_t * trail,apr_pool_t * pool)841 svn_fs_base__dag_delete(dag_node_t *parent,
842                         const char *name,
843                         const char *txn_id,
844                         trail_t *trail,
845                         apr_pool_t *pool)
846 {
847   node_revision_t *parent_noderev;
848   const char *rep_key, *mutable_rep_key;
849   apr_hash_t *entries = NULL;
850   svn_skel_t *entries_skel;
851   svn_fs_t *fs = parent->fs;
852   svn_string_t str;
853   svn_fs_id_t *id = NULL;
854   dag_node_t *node;
855 
856   /* Make sure parent is a directory. */
857   if (parent->kind != svn_node_dir)
858     return svn_error_createf
859       (SVN_ERR_FS_NOT_DIRECTORY, NULL,
860        _("Attempted to delete entry '%s' from *non*-directory node"), name);
861 
862   /* Make sure parent is mutable. */
863   if (! svn_fs_base__dag_check_mutable(parent, txn_id))
864     return svn_error_createf
865       (SVN_ERR_FS_NOT_MUTABLE, NULL,
866        _("Attempted to delete entry '%s' from immutable directory node"),
867        name);
868 
869   /* Make sure that NAME is a single path component. */
870   if (! svn_path_is_single_path_component(name))
871     return svn_error_createf
872       (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
873        _("Attempted to delete a node with an illegal name '%s'"), name);
874 
875   /* Get a fresh NODE-REVISION for the parent node. */
876   SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
877                                         trail, pool));
878 
879   /* Get the key for the parent's entries list (data) representation. */
880   rep_key = parent_noderev->data_key;
881 
882   /* No REP_KEY means no representation, and no representation means
883      no data, and no data means no entries...there's nothing here to
884      delete! */
885   if (! rep_key)
886     return svn_error_createf
887       (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
888        _("Delete failed: directory has no entry '%s'"), name);
889 
890   /* Ensure we have a key to a mutable representation of the entries
891      list.  We'll have to update the NODE-REVISION if it points to an
892      immutable version.  */
893   SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
894                                        fs, txn_id, trail, pool));
895   if (! svn_fs_base__same_keys(mutable_rep_key, rep_key))
896     {
897       parent_noderev->data_key = mutable_rep_key;
898       SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
899                                             trail, pool));
900     }
901 
902   /* Read the representation, then use it to get the string that holds
903      the entries list.  Parse that list into a skel, and parse *that*
904      into a hash. */
905 
906   SVN_ERR(svn_fs_base__rep_contents(&str, fs, rep_key, trail, pool));
907   entries_skel = svn_skel__parse(str.data, str.len, pool);
908   if (entries_skel)
909     SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel, pool));
910 
911   /* Find NAME in the ENTRIES skel.  */
912   if (entries)
913     id = svn_hash_gets(entries, name);
914 
915   /* If we never found ID in ENTRIES (perhaps because there are no
916      ENTRIES, perhaps because ID just isn't in the existing ENTRIES
917      ... it doesn't matter), return an error.  */
918   if (! id)
919     return svn_error_createf
920       (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
921        _("Delete failed: directory has no entry '%s'"), name);
922 
923   /* Use the ID of this ENTRY to get the entry's node.  */
924   SVN_ERR(svn_fs_base__dag_get_node(&node, svn_fs_base__dag_get_fs(parent),
925                                     id, trail, pool));
926 
927   /* If mutable, remove it and any mutable children from db. */
928   SVN_ERR(svn_fs_base__dag_delete_if_mutable(parent->fs, id, txn_id,
929                                              trail, pool));
930 
931   /* Remove this entry from its parent's entries list. */
932   svn_hash_sets(entries, name, NULL);
933 
934   /* Replace the old entries list with the new one. */
935   {
936     svn_stream_t *ws;
937     svn_stringbuf_t *unparsed_entries;
938     apr_size_t len;
939 
940     SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries, pool));
941     unparsed_entries = svn_skel__unparse(entries_skel, pool);
942     SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
943                                                    txn_id, TRUE, trail,
944                                                    pool));
945     len = unparsed_entries->len;
946     SVN_ERR(svn_stream_write(ws, unparsed_entries->data, &len));
947     SVN_ERR(svn_stream_close(ws));
948   }
949 
950   return SVN_NO_ERROR;
951 }
952 
953 
954 svn_error_t *
svn_fs_base__dag_remove_node(svn_fs_t * fs,const svn_fs_id_t * id,const char * txn_id,trail_t * trail,apr_pool_t * pool)955 svn_fs_base__dag_remove_node(svn_fs_t *fs,
956                              const svn_fs_id_t *id,
957                              const char *txn_id,
958                              trail_t *trail,
959                              apr_pool_t *pool)
960 {
961   dag_node_t *node;
962   node_revision_t *noderev;
963 
964   /* Fetch the node. */
965   SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
966 
967   /* If immutable, do nothing and return immediately. */
968   if (! svn_fs_base__dag_check_mutable(node, txn_id))
969     return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
970                              _("Attempted removal of immutable node"));
971 
972   /* Get a fresh node-revision. */
973   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool));
974 
975   /* Delete any mutable property representation. */
976   if (noderev->prop_key)
977     SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key,
978                                                txn_id, trail, pool));
979 
980   /* Delete any mutable data representation. */
981   if (noderev->data_key)
982     SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->data_key,
983                                                txn_id, trail, pool));
984 
985   /* Delete any mutable edit representation (files only). */
986   if (noderev->edit_key)
987     SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
988                                                txn_id, trail, pool));
989 
990   /* Delete the node revision itself. */
991   return svn_fs_base__delete_node_revision(fs, id,
992                                            noderev->predecessor_id == NULL,
993                                            trail, pool);
994 }
995 
996 
997 svn_error_t *
svn_fs_base__dag_delete_if_mutable(svn_fs_t * fs,const svn_fs_id_t * id,const char * txn_id,trail_t * trail,apr_pool_t * pool)998 svn_fs_base__dag_delete_if_mutable(svn_fs_t *fs,
999                                    const svn_fs_id_t *id,
1000                                    const char *txn_id,
1001                                    trail_t *trail,
1002                                    apr_pool_t *pool)
1003 {
1004   dag_node_t *node;
1005 
1006   /* Get the node. */
1007   SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
1008 
1009   /* If immutable, do nothing and return immediately. */
1010   if (! svn_fs_base__dag_check_mutable(node, txn_id))
1011     return SVN_NO_ERROR;
1012 
1013   /* Else it's mutable.  Recurse on directories... */
1014   if (node->kind == svn_node_dir)
1015     {
1016       apr_hash_t *entries;
1017       apr_hash_index_t *hi;
1018 
1019       /* Loop over hash entries */
1020       SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, pool));
1021       if (entries)
1022         {
1023           apr_pool_t *subpool = svn_pool_create(pool);
1024           for (hi = apr_hash_first(pool, entries);
1025                hi;
1026                hi = apr_hash_next(hi))
1027             {
1028               void *val;
1029               svn_fs_dirent_t *dirent;
1030 
1031               svn_pool_clear(subpool);
1032               apr_hash_this(hi, NULL, NULL, &val);
1033               dirent = val;
1034               SVN_ERR(svn_fs_base__dag_delete_if_mutable(fs, dirent->id,
1035                                                          txn_id, trail,
1036                                                          subpool));
1037             }
1038           svn_pool_destroy(subpool);
1039         }
1040     }
1041 
1042   /* ... then delete the node itself, any mutable representations and
1043      strings it points to, and possibly its node-origins record. */
1044   return svn_fs_base__dag_remove_node(fs, id, txn_id, trail, pool);
1045 }
1046 
1047 
1048 svn_error_t *
svn_fs_base__dag_make_file(dag_node_t ** child_p,dag_node_t * parent,const char * parent_path,const char * name,const char * txn_id,trail_t * trail,apr_pool_t * pool)1049 svn_fs_base__dag_make_file(dag_node_t **child_p,
1050                            dag_node_t *parent,
1051                            const char *parent_path,
1052                            const char *name,
1053                            const char *txn_id,
1054                            trail_t *trail,
1055                            apr_pool_t *pool)
1056 {
1057   /* Call our little helper function */
1058   return make_entry(child_p, parent, parent_path, name, FALSE,
1059                     txn_id, trail, pool);
1060 }
1061 
1062 
1063 svn_error_t *
svn_fs_base__dag_make_dir(dag_node_t ** child_p,dag_node_t * parent,const char * parent_path,const char * name,const char * txn_id,trail_t * trail,apr_pool_t * pool)1064 svn_fs_base__dag_make_dir(dag_node_t **child_p,
1065                           dag_node_t *parent,
1066                           const char *parent_path,
1067                           const char *name,
1068                           const char *txn_id,
1069                           trail_t *trail,
1070                           apr_pool_t *pool)
1071 {
1072   /* Call our little helper function */
1073   return make_entry(child_p, parent, parent_path, name, TRUE,
1074                     txn_id, trail, pool);
1075 }
1076 
1077 
1078 svn_error_t *
svn_fs_base__dag_get_contents(svn_stream_t ** contents,dag_node_t * file,trail_t * trail,apr_pool_t * pool)1079 svn_fs_base__dag_get_contents(svn_stream_t **contents,
1080                               dag_node_t *file,
1081                               trail_t *trail,
1082                               apr_pool_t *pool)
1083 {
1084   node_revision_t *noderev;
1085 
1086   /* Make sure our node is a file. */
1087   if (file->kind != svn_node_file)
1088     return svn_error_createf
1089       (SVN_ERR_FS_NOT_FILE, NULL,
1090        _("Attempted to get textual contents of a *non*-file node"));
1091 
1092   /* Go get a fresh node-revision for FILE. */
1093   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1094                                         trail, pool));
1095 
1096   /* Our job is to _return_ a stream on the file's contents, so the
1097      stream has to be trail-independent.  Here, we pass NULL to tell
1098      the stream that we're not providing it a trail that lives across
1099      reads.  This means the stream will do each read in a one-off,
1100      temporary trail.  */
1101   return svn_fs_base__rep_contents_read_stream(contents, file->fs,
1102                                                noderev->data_key,
1103                                                FALSE, trail, pool);
1104 
1105   /* Note that we're not registering any `close' func, because there's
1106      nothing to cleanup outside of our trail.  When the trail is
1107      freed, the stream/baton will be too. */
1108 }
1109 
1110 
1111 svn_error_t *
svn_fs_base__dag_file_length(svn_filesize_t * length,dag_node_t * file,trail_t * trail,apr_pool_t * pool)1112 svn_fs_base__dag_file_length(svn_filesize_t *length,
1113                              dag_node_t *file,
1114                              trail_t *trail,
1115                              apr_pool_t *pool)
1116 {
1117   node_revision_t *noderev;
1118 
1119   /* Make sure our node is a file. */
1120   if (file->kind != svn_node_file)
1121     return svn_error_createf
1122       (SVN_ERR_FS_NOT_FILE, NULL,
1123        _("Attempted to get length of a *non*-file node"));
1124 
1125   /* Go get a fresh node-revision for FILE, and . */
1126   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1127                                         trail, pool));
1128   if (noderev->data_key)
1129     SVN_ERR(svn_fs_base__rep_contents_size(length, file->fs,
1130                                            noderev->data_key, trail, pool));
1131   else
1132     *length = 0;
1133 
1134   return SVN_NO_ERROR;
1135 }
1136 
1137 
1138 svn_error_t *
svn_fs_base__dag_file_checksum(svn_checksum_t ** checksum,svn_checksum_kind_t checksum_kind,dag_node_t * file,trail_t * trail,apr_pool_t * pool)1139 svn_fs_base__dag_file_checksum(svn_checksum_t **checksum,
1140                                svn_checksum_kind_t checksum_kind,
1141                                dag_node_t *file,
1142                                trail_t *trail,
1143                                apr_pool_t *pool)
1144 {
1145   node_revision_t *noderev;
1146 
1147   if (file->kind != svn_node_file)
1148     return svn_error_createf
1149       (SVN_ERR_FS_NOT_FILE, NULL,
1150        _("Attempted to get checksum of a *non*-file node"));
1151 
1152   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1153                                         trail, pool));
1154   if (! noderev->data_key)
1155     {
1156       *checksum = NULL;
1157       return SVN_NO_ERROR;
1158     }
1159 
1160   if (checksum_kind == svn_checksum_md5)
1161     return svn_fs_base__rep_contents_checksums(checksum, NULL, file->fs,
1162                                                noderev->data_key,
1163                                                trail, pool);
1164   else if (checksum_kind == svn_checksum_sha1)
1165     return svn_fs_base__rep_contents_checksums(NULL, checksum, file->fs,
1166                                                noderev->data_key,
1167                                                trail, pool);
1168   else
1169     return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
1170 }
1171 
1172 
1173 svn_error_t *
svn_fs_base__dag_get_edit_stream(svn_stream_t ** contents,dag_node_t * file,const char * txn_id,trail_t * trail,apr_pool_t * pool)1174 svn_fs_base__dag_get_edit_stream(svn_stream_t **contents,
1175                                  dag_node_t *file,
1176                                  const char *txn_id,
1177                                  trail_t *trail,
1178                                  apr_pool_t *pool)
1179 {
1180   svn_fs_t *fs = file->fs;   /* just for nicer indentation */
1181   node_revision_t *noderev;
1182   const char *mutable_rep_key;
1183   svn_stream_t *ws;
1184 
1185   /* Make sure our node is a file. */
1186   if (file->kind != svn_node_file)
1187     return svn_error_createf
1188       (SVN_ERR_FS_NOT_FILE, NULL,
1189        _("Attempted to set textual contents of a *non*-file node"));
1190 
1191   /* Make sure our node is mutable. */
1192   if (! svn_fs_base__dag_check_mutable(file, txn_id))
1193     return svn_error_createf
1194       (SVN_ERR_FS_NOT_MUTABLE, NULL,
1195        _("Attempted to set textual contents of an immutable node"));
1196 
1197   /* Get the node revision. */
1198   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
1199                                         trail, pool));
1200 
1201   /* If this node already has an EDIT-DATA-KEY, destroy the data
1202      associated with that key.  */
1203   if (noderev->edit_key)
1204     SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
1205                                                txn_id, trail, pool));
1206 
1207   /* Now, let's ensure that we have a new EDIT-DATA-KEY available for
1208      use. */
1209   SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, NULL, fs,
1210                                        txn_id, trail, pool));
1211 
1212   /* We made a new rep, so update the node revision. */
1213   noderev->edit_key = mutable_rep_key;
1214   SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev,
1215                                         trail, pool));
1216 
1217   /* Return a writable stream with which to set new contents. */
1218   SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
1219                                                  txn_id, FALSE, trail,
1220                                                  pool));
1221   *contents = ws;
1222 
1223   return SVN_NO_ERROR;
1224 }
1225 
1226 
1227 
1228 svn_error_t *
svn_fs_base__dag_finalize_edits(dag_node_t * file,const svn_checksum_t * checksum,const char * txn_id,trail_t * trail,apr_pool_t * pool)1229 svn_fs_base__dag_finalize_edits(dag_node_t *file,
1230                                 const svn_checksum_t *checksum,
1231                                 const char *txn_id,
1232                                 trail_t *trail,
1233                                 apr_pool_t *pool)
1234 {
1235   svn_fs_t *fs = file->fs;   /* just for nicer indentation */
1236   node_revision_t *noderev;
1237   const char *old_data_key, *new_data_key, *useless_data_key = NULL;
1238   const char *data_key_uniquifier = NULL;
1239   svn_checksum_t *md5_checksum, *sha1_checksum;
1240   base_fs_data_t *bfd = fs->fsap_data;
1241 
1242   /* Make sure our node is a file. */
1243   if (file->kind != svn_node_file)
1244     return svn_error_createf
1245       (SVN_ERR_FS_NOT_FILE, NULL,
1246        _("Attempted to set textual contents of a *non*-file node"));
1247 
1248   /* Make sure our node is mutable. */
1249   if (! svn_fs_base__dag_check_mutable(file, txn_id))
1250     return svn_error_createf
1251       (SVN_ERR_FS_NOT_MUTABLE, NULL,
1252        _("Attempted to set textual contents of an immutable node"));
1253 
1254   /* Get the node revision. */
1255   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
1256                                         trail, pool));
1257 
1258   /* If this node has no EDIT-DATA-KEY, this is a no-op. */
1259   if (! noderev->edit_key)
1260     return SVN_NO_ERROR;
1261 
1262   /* Get our representation's checksums. */
1263   SVN_ERR(svn_fs_base__rep_contents_checksums(&md5_checksum, &sha1_checksum,
1264                                               fs, noderev->edit_key,
1265                                               trail, pool));
1266 
1267   /* If our caller provided a checksum of the right kind to compare, do so. */
1268   if (checksum)
1269     {
1270       svn_checksum_t *test_checksum;
1271 
1272       if (checksum->kind == svn_checksum_md5)
1273         test_checksum = md5_checksum;
1274       else if (checksum->kind == svn_checksum_sha1)
1275         test_checksum = sha1_checksum;
1276       else
1277         return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
1278 
1279       if (! svn_checksum_match(checksum, test_checksum))
1280         return svn_checksum_mismatch_err(checksum, test_checksum, pool,
1281                         _("Checksum mismatch on representation '%s'"),
1282                         noderev->edit_key);
1283     }
1284 
1285   /* Now, we want to delete the old representation and replace it with
1286      the new.  Of course, we don't actually delete anything until
1287      everything is being properly referred to by the node-revision
1288      skel.
1289 
1290      Now, if the result of all this editing is that we've created a
1291      representation that describes content already represented
1292      immutably in our database, we don't even need to keep these edits.
1293      We can simply point our data_key at that pre-existing
1294      representation and throw away our work!  In this situation,
1295      though, we'll need a unique ID to help other code distinguish
1296      between "the contents weren't touched" and "the contents were
1297      touched but still look the same" (to state it oversimply).  */
1298   old_data_key = noderev->data_key;
1299   if (sha1_checksum && bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
1300     {
1301       svn_error_t *err = svn_fs_bdb__get_checksum_rep(&new_data_key, fs,
1302                                                       sha1_checksum,
1303                                                       trail, pool);
1304       if (! err)
1305         {
1306           useless_data_key = noderev->edit_key;
1307           err = svn_fs_bdb__reserve_rep_reuse_id(&data_key_uniquifier,
1308                                                  trail->fs, trail, pool);
1309         }
1310       else if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_CHECKSUM_REP))
1311         {
1312           svn_error_clear(err);
1313           err = SVN_NO_ERROR;
1314           new_data_key = noderev->edit_key;
1315         }
1316       SVN_ERR(err);
1317     }
1318   else
1319     {
1320       new_data_key = noderev->edit_key;
1321     }
1322 
1323   noderev->data_key = new_data_key;
1324   noderev->data_key_uniquifier = data_key_uniquifier;
1325   noderev->edit_key = NULL;
1326 
1327   SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, trail, pool));
1328 
1329   /* Only *now* can we safely destroy the old representation (if it
1330      even existed in the first place). */
1331   if (old_data_key)
1332     SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, old_data_key, txn_id,
1333                                                trail, pool));
1334 
1335   /* If we've got a discardable rep (probably because we ended up
1336      re-using a preexisting one), throw out the discardable rep. */
1337   if (useless_data_key)
1338     SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, useless_data_key,
1339                                                txn_id, trail, pool));
1340 
1341   return SVN_NO_ERROR;
1342 }
1343 
1344 
1345 
1346 dag_node_t *
svn_fs_base__dag_dup(const dag_node_t * node,apr_pool_t * pool)1347 svn_fs_base__dag_dup(const dag_node_t *node,
1348                      apr_pool_t *pool)
1349 {
1350   /* Allocate our new node. */
1351   dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node));
1352 
1353   new_node->fs = node->fs;
1354   new_node->id = svn_fs_base__id_copy(node->id, pool);
1355   new_node->kind = node->kind;
1356   new_node->created_path = apr_pstrdup(pool, node->created_path);
1357   return new_node;
1358 }
1359 
1360 
1361 svn_error_t *
svn_fs_base__dag_open(dag_node_t ** child_p,dag_node_t * parent,const char * name,trail_t * trail,apr_pool_t * pool)1362 svn_fs_base__dag_open(dag_node_t **child_p,
1363                       dag_node_t *parent,
1364                       const char *name,
1365                       trail_t *trail,
1366                       apr_pool_t *pool)
1367 {
1368   const svn_fs_id_t *node_id;
1369 
1370   /* Ensure that NAME exists in PARENT's entry list. */
1371   SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, trail, pool));
1372   if (! node_id)
1373     return svn_error_createf
1374       (SVN_ERR_FS_NOT_FOUND, NULL,
1375        _("Attempted to open non-existent child node '%s'"), name);
1376 
1377   /* Make sure that NAME is a single path component. */
1378   if (! svn_path_is_single_path_component(name))
1379     return svn_error_createf
1380       (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
1381        _("Attempted to open node with an illegal name '%s'"), name);
1382 
1383   /* Now get the node that was requested. */
1384   return svn_fs_base__dag_get_node(child_p, svn_fs_base__dag_get_fs(parent),
1385                                    node_id, trail, pool);
1386 }
1387 
1388 
1389 svn_error_t *
svn_fs_base__dag_copy(dag_node_t * to_node,const char * entry,dag_node_t * from_node,svn_boolean_t preserve_history,svn_revnum_t from_rev,const char * from_path,const char * txn_id,trail_t * trail,apr_pool_t * pool)1390 svn_fs_base__dag_copy(dag_node_t *to_node,
1391                       const char *entry,
1392                       dag_node_t *from_node,
1393                       svn_boolean_t preserve_history,
1394                       svn_revnum_t from_rev,
1395                       const char *from_path,
1396                       const char *txn_id,
1397                       trail_t *trail,
1398                       apr_pool_t *pool)
1399 {
1400   const svn_fs_id_t *id;
1401 
1402   if (preserve_history)
1403     {
1404       node_revision_t *noderev;
1405       const char *copy_id;
1406       svn_fs_t *fs = svn_fs_base__dag_get_fs(from_node);
1407       const svn_fs_id_t *src_id = svn_fs_base__dag_get_id(from_node);
1408       const char *from_txn_id = NULL;
1409 
1410       /* Make a copy of the original node revision. */
1411       SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, from_node->id,
1412                                             trail, pool));
1413 
1414       /* Reserve a copy ID for this new copy. */
1415       SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, pool));
1416 
1417       /* Create a successor with its predecessor pointing at the copy
1418          source. */
1419       noderev->predecessor_id = svn_fs_base__id_copy(src_id, pool);
1420       if (noderev->predecessor_count != -1)
1421         noderev->predecessor_count++;
1422       noderev->created_path = svn_fspath__join
1423         (svn_fs_base__dag_get_created_path(to_node), entry, pool);
1424       SVN_ERR(svn_fs_base__create_successor(&id, fs, src_id, noderev,
1425                                             copy_id, txn_id, trail, pool));
1426 
1427       /* Translate FROM_REV into a transaction ID. */
1428       SVN_ERR(svn_fs_base__rev_get_txn_id(&from_txn_id, fs, from_rev,
1429                                           trail, pool));
1430 
1431       /* Now that we've done the copy, we need to add the information
1432          about the copy to the `copies' table, using the COPY_ID we
1433          reserved above.  */
1434       SVN_ERR(svn_fs_bdb__create_copy
1435               (fs, copy_id,
1436                svn_fs__canonicalize_abspath(from_path, pool),
1437                from_txn_id, id, copy_kind_real, trail, pool));
1438 
1439       /* Finally, add the COPY_ID to the transaction's list of copies
1440          so that, if this transaction is aborted, the `copies' table
1441          entry we added above will be cleaned up. */
1442       SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id, trail, pool));
1443     }
1444   else  /* don't preserve history */
1445     {
1446       id = svn_fs_base__dag_get_id(from_node);
1447     }
1448 
1449   /* Set the entry in to_node to the new id. */
1450   return svn_fs_base__dag_set_entry(to_node, entry, id, txn_id,
1451                                     trail, pool);
1452 }
1453 
1454 
1455 
1456 /*** Deltification ***/
1457 
1458 /* Maybe change the representation identified by TARGET_REP_KEY to be
1459    a delta against the representation identified by SOURCE_REP_KEY.
1460    Some reasons why we wouldn't include:
1461 
1462       - TARGET_REP_KEY and SOURCE_REP_KEY are the same key.
1463 
1464       - TARGET_REP_KEY's representation isn't mutable in TXN_ID (if
1465         TXN_ID is non-NULL).
1466 
1467       - The delta provides less space savings that a fulltext (this is
1468         a detail handled by lower logic layers, not this function).
1469 
1470    Do this work in TRAIL, using POOL for necessary allocations.
1471 */
1472 static svn_error_t *
maybe_deltify_mutable_rep(const char * target_rep_key,const char * source_rep_key,const char * txn_id,trail_t * trail,apr_pool_t * pool)1473 maybe_deltify_mutable_rep(const char *target_rep_key,
1474                           const char *source_rep_key,
1475                           const char *txn_id,
1476                           trail_t *trail,
1477                           apr_pool_t *pool)
1478 {
1479   if (! (target_rep_key && source_rep_key
1480          && (strcmp(target_rep_key, source_rep_key) != 0)))
1481     return SVN_NO_ERROR;
1482 
1483   if (txn_id)
1484     {
1485       representation_t *target_rep;
1486       SVN_ERR(svn_fs_bdb__read_rep(&target_rep, trail->fs, target_rep_key,
1487                                    trail, pool));
1488       if (strcmp(target_rep->txn_id, txn_id) != 0)
1489         return SVN_NO_ERROR;
1490     }
1491 
1492   return svn_fs_base__rep_deltify(trail->fs, target_rep_key, source_rep_key,
1493                                   trail, pool);
1494 }
1495 
1496 
1497 svn_error_t *
svn_fs_base__dag_deltify(dag_node_t * target,dag_node_t * source,svn_boolean_t props_only,const char * txn_id,trail_t * trail,apr_pool_t * pool)1498 svn_fs_base__dag_deltify(dag_node_t *target,
1499                          dag_node_t *source,
1500                          svn_boolean_t props_only,
1501                          const char *txn_id,
1502                          trail_t *trail,
1503                          apr_pool_t *pool)
1504 {
1505   node_revision_t *source_nr, *target_nr;
1506   svn_fs_t *fs = svn_fs_base__dag_get_fs(target);
1507 
1508   /* Get node revisions for the two nodes.  */
1509   SVN_ERR(svn_fs_bdb__get_node_revision(&target_nr, fs, target->id,
1510                                         trail, pool));
1511   SVN_ERR(svn_fs_bdb__get_node_revision(&source_nr, fs, source->id,
1512                                         trail, pool));
1513 
1514   /* If TARGET and SOURCE both have properties, and are not sharing a
1515      property key, deltify TARGET's properties.  */
1516   SVN_ERR(maybe_deltify_mutable_rep(target_nr->prop_key, source_nr->prop_key,
1517                                     txn_id, trail, pool));
1518 
1519   /* If we are not only attending to properties, and if TARGET and
1520      SOURCE both have data, and are not sharing a data key, deltify
1521      TARGET's data.  */
1522   if (! props_only)
1523     SVN_ERR(maybe_deltify_mutable_rep(target_nr->data_key, source_nr->data_key,
1524                                       txn_id, trail, pool));
1525 
1526   return SVN_NO_ERROR;
1527 }
1528 
1529 /* Maybe store a `checksum-reps' index record for the representation whose
1530    key is REP.  (If there's already a rep for this checksum, we don't
1531    bother overwriting it.)  */
1532 static svn_error_t *
maybe_store_checksum_rep(const char * rep,trail_t * trail,apr_pool_t * pool)1533 maybe_store_checksum_rep(const char *rep,
1534                          trail_t *trail,
1535                          apr_pool_t *pool)
1536 {
1537   svn_error_t *err = SVN_NO_ERROR;
1538   svn_fs_t *fs = trail->fs;
1539   svn_checksum_t *sha1_checksum;
1540 
1541   /* We want the SHA1 checksum, if any. */
1542   SVN_ERR(svn_fs_base__rep_contents_checksums(NULL, &sha1_checksum,
1543                                               fs, rep, trail, pool));
1544   if (sha1_checksum)
1545     {
1546       err = svn_fs_bdb__set_checksum_rep(fs, sha1_checksum, rep, trail, pool);
1547       if (err && (err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
1548         {
1549           svn_error_clear(err);
1550           err = SVN_NO_ERROR;
1551         }
1552     }
1553   return svn_error_trace(err);
1554 }
1555 
1556 svn_error_t *
svn_fs_base__dag_index_checksums(dag_node_t * node,trail_t * trail,apr_pool_t * pool)1557 svn_fs_base__dag_index_checksums(dag_node_t *node,
1558                                  trail_t *trail,
1559                                  apr_pool_t *pool)
1560 {
1561   node_revision_t *node_rev;
1562 
1563   SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, trail->fs, node->id,
1564                                         trail, pool));
1565   if ((node_rev->kind == svn_node_file) && node_rev->data_key)
1566     SVN_ERR(maybe_store_checksum_rep(node_rev->data_key, trail, pool));
1567   if (node_rev->prop_key)
1568     SVN_ERR(maybe_store_checksum_rep(node_rev->prop_key, trail, pool));
1569 
1570   return SVN_NO_ERROR;
1571 }
1572 
1573 
1574 
1575 /*** Committing ***/
1576 
1577 svn_error_t *
svn_fs_base__dag_commit_txn(svn_revnum_t * new_rev,svn_fs_txn_t * txn,trail_t * trail,apr_pool_t * pool)1578 svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev,
1579                             svn_fs_txn_t *txn,
1580                             trail_t *trail,
1581                             apr_pool_t *pool)
1582 {
1583   revision_t revision;
1584   apr_hash_t *txnprops;
1585   svn_fs_t *fs = txn->fs;
1586   const char *txn_id = txn->id;
1587   const svn_string_t *client_date;
1588 
1589   /* Remove any temporary transaction properties initially created by
1590      begin_txn().  */
1591   SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, txn_id, trail));
1592 
1593   /* Add new revision entry to `revisions' table. */
1594   revision.txn_id = txn_id;
1595   *new_rev = SVN_INVALID_REVNUM;
1596   SVN_ERR(svn_fs_bdb__put_rev(new_rev, fs, &revision, trail, pool));
1597 
1598   if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
1599     SVN_ERR(svn_fs_base__set_txn_prop
1600             (fs, txn_id, SVN_FS__PROP_TXN_CHECK_OOD, NULL, trail, pool));
1601 
1602   if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
1603     SVN_ERR(svn_fs_base__set_txn_prop
1604             (fs, txn_id, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, trail, pool));
1605 
1606   client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE);
1607   if (client_date)
1608     SVN_ERR(svn_fs_base__set_txn_prop
1609             (fs, txn_id, SVN_FS__PROP_TXN_CLIENT_DATE, NULL, trail, pool));
1610 
1611   /* Promote the unfinished transaction to a committed one. */
1612   SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, *new_rev,
1613                                           trail, pool));
1614 
1615   if (!client_date || strcmp(client_date->data, "1"))
1616     {
1617       /* Set a date on the commit if requested.  We wait until now to fetch the
1618          date, so it's definitely newer than any previous revision's date. */
1619       svn_string_t date;
1620       date.data = svn_time_to_cstring(apr_time_now(), pool);
1621       date.len = strlen(date.data);
1622       SVN_ERR(svn_fs_base__set_rev_prop(fs, *new_rev, SVN_PROP_REVISION_DATE,
1623                                         NULL, &date, trail, pool));
1624     }
1625 
1626   return SVN_NO_ERROR;
1627 }
1628 
1629 
1630 /*** Comparison. ***/
1631 
1632 svn_error_t *
svn_fs_base__things_different(svn_boolean_t * props_changed,svn_boolean_t * contents_changed,dag_node_t * node1,dag_node_t * node2,trail_t * trail,apr_pool_t * pool)1633 svn_fs_base__things_different(svn_boolean_t *props_changed,
1634                               svn_boolean_t *contents_changed,
1635                               dag_node_t *node1,
1636                               dag_node_t *node2,
1637                               trail_t *trail,
1638                               apr_pool_t *pool)
1639 {
1640   node_revision_t *noderev1, *noderev2;
1641 
1642   /* If we have no place to store our results, don't bother doing
1643      anything. */
1644   if (! props_changed && ! contents_changed)
1645     return SVN_NO_ERROR;
1646 
1647   /* The node revision skels for these two nodes. */
1648   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev1, node1->fs, node1->id,
1649                                         trail, pool));
1650   SVN_ERR(svn_fs_bdb__get_node_revision(&noderev2, node2->fs, node2->id,
1651                                         trail, pool));
1652 
1653   /* Compare property keys. */
1654   if (props_changed != NULL)
1655     *props_changed = (! svn_fs_base__same_keys(noderev1->prop_key,
1656                                                noderev2->prop_key));
1657 
1658   /* Compare contents keys and their (optional) uniquifiers. */
1659   if (contents_changed != NULL)
1660     *contents_changed =
1661       (! (svn_fs_base__same_keys(noderev1->data_key,
1662                                  noderev2->data_key)
1663           /* Technically, these uniquifiers aren't used and "keys",
1664              but keys are base-36 stringified numbers, so we'll take
1665              this liberty. */
1666           && (svn_fs_base__same_keys(noderev1->data_key_uniquifier,
1667                                      noderev2->data_key_uniquifier))));
1668 
1669   return SVN_NO_ERROR;
1670 }
1671 
1672 
1673 
1674 /*** Mergeinfo tracking stuff ***/
1675 
1676 svn_error_t *
svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t * has_mergeinfo,apr_int64_t * count,dag_node_t * node,trail_t * trail,apr_pool_t * pool)1677 svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo,
1678                                      apr_int64_t *count,
1679                                      dag_node_t *node,
1680                                      trail_t *trail,
1681                                      apr_pool_t *pool)
1682 {
1683   node_revision_t *node_rev;
1684   svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1685   const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1686 
1687   SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1688   if (has_mergeinfo)
1689     *has_mergeinfo = node_rev->has_mergeinfo;
1690   if (count)
1691     *count = node_rev->mergeinfo_count;
1692   return SVN_NO_ERROR;
1693 }
1694 
1695 
1696 svn_error_t *
svn_fs_base__dag_set_has_mergeinfo(dag_node_t * node,svn_boolean_t has_mergeinfo,svn_boolean_t * had_mergeinfo,const char * txn_id,trail_t * trail,apr_pool_t * pool)1697 svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node,
1698                                    svn_boolean_t has_mergeinfo,
1699                                    svn_boolean_t *had_mergeinfo,
1700                                    const char *txn_id,
1701                                    trail_t *trail,
1702                                    apr_pool_t *pool)
1703 {
1704   node_revision_t *node_rev;
1705   svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1706   const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1707 
1708   SVN_ERR(svn_fs_base__test_required_feature_format
1709           (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
1710 
1711   if (! svn_fs_base__dag_check_mutable(node, txn_id))
1712     return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
1713                              _("Attempted merge tracking info change on "
1714                                "immutable node"));
1715 
1716   SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1717   *had_mergeinfo = node_rev->has_mergeinfo;
1718 
1719   /* Are we changing the node? */
1720   if ((! has_mergeinfo) != (! *had_mergeinfo))
1721     {
1722       /* Note the new has-mergeinfo state. */
1723       node_rev->has_mergeinfo = has_mergeinfo;
1724 
1725       /* Increment or decrement the mergeinfo count as necessary. */
1726       if (has_mergeinfo)
1727         node_rev->mergeinfo_count++;
1728       else
1729         node_rev->mergeinfo_count--;
1730 
1731       SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool));
1732     }
1733   return SVN_NO_ERROR;
1734 }
1735 
1736 
1737 svn_error_t *
svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t * node,apr_int64_t count_delta,const char * txn_id,trail_t * trail,apr_pool_t * pool)1738 svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node,
1739                                         apr_int64_t count_delta,
1740                                         const char *txn_id,
1741                                         trail_t *trail,
1742                                         apr_pool_t *pool)
1743 {
1744   node_revision_t *node_rev;
1745   svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1746   const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1747 
1748   SVN_ERR(svn_fs_base__test_required_feature_format
1749           (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
1750 
1751   if (! svn_fs_base__dag_check_mutable(node, txn_id))
1752     return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
1753                              _("Attempted mergeinfo count change on "
1754                                "immutable node"));
1755 
1756   if (count_delta == 0)
1757     return SVN_NO_ERROR;
1758 
1759   SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1760   node_rev->mergeinfo_count = node_rev->mergeinfo_count + count_delta;
1761   if ((node_rev->mergeinfo_count < 0)
1762       || ((node->kind == svn_node_file) && (node_rev->mergeinfo_count > 1)))
1763     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1764                              apr_psprintf(pool,
1765                                           _("Invalid value (%%%s) for node "
1766                                             "revision mergeinfo count"),
1767                                           APR_INT64_T_FMT),
1768                              node_rev->mergeinfo_count);
1769 
1770   return svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool);
1771 }
1772