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(©_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(©_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