1 /* fs-test.c --- tests for the filesystem
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 <stdlib.h>
24 #include <string.h>
25 #include <apr_pools.h>
26 #include <apr_thread_proc.h>
27 #include <apr_poll.h>
28 #include <assert.h>
29 
30 #include "../svn_test.h"
31 
32 #include "svn_hash.h"
33 #include "svn_pools.h"
34 #include "svn_time.h"
35 #include "svn_string.h"
36 #include "svn_fs.h"
37 #include "svn_checksum.h"
38 #include "svn_mergeinfo.h"
39 #include "svn_props.h"
40 #include "svn_version.h"
41 
42 #include "svn_private_config.h"
43 #include "private/svn_cache.h"
44 #include "private/svn_fs_util.h"
45 #include "private/svn_fs_private.h"
46 #include "private/svn_fspath.h"
47 #include "private/svn_sqlite.h"
48 
49 #include "../svn_test_fs.h"
50 
51 #include "../../libsvn_delta/delta.h"
52 #include "../../libsvn_fs/fs-loader.h"
53 
54 #define SET_STR(ps, s) ((ps)->data = (s), (ps)->len = strlen(s))
55 
56 
57 /*-----------------------------------------------------------------*/
58 
59 /** The actual fs-tests called by `make check` **/
60 
61 /* Helper:  commit TXN, expecting either success or failure:
62  *
63  * If EXPECTED_CONFLICT is null, then the commit is expected to
64  * succeed.  If it does succeed, set *NEW_REV to the new revision;
65  * else return error.
66  *
67  * If EXPECTED_CONFLICT is non-null, it is either the empty string or
68  * the expected path of the conflict.  If it is the empty string, any
69  * conflict is acceptable.  If it is a non-empty string, the commit
70  * must fail due to conflict, and the conflict path must match
71  * EXPECTED_CONFLICT.  If they don't match, return error.
72  *
73  * If a conflict is expected but the commit succeeds anyway, return
74  * error.  If the commit fails but does not provide an error, return
75  * error.
76  */
77 static svn_error_t *
test_commit_txn(svn_revnum_t * new_rev,svn_fs_txn_t * txn,const char * expected_conflict,apr_pool_t * pool)78 test_commit_txn(svn_revnum_t *new_rev,
79                 svn_fs_txn_t *txn,
80                 const char *expected_conflict,
81                 apr_pool_t *pool)
82 {
83   const char *conflict;
84   svn_error_t *err;
85 
86   err = svn_fs_commit_txn(&conflict, new_rev, txn, pool);
87 
88   if (err && (err->apr_err == SVN_ERR_FS_CONFLICT))
89     {
90       svn_error_clear(err);
91       if (! expected_conflict)
92         {
93           return svn_error_createf
94             (SVN_ERR_FS_CONFLICT, NULL,
95              "commit conflicted at '%s', but no conflict expected",
96              conflict ? conflict : "(missing conflict info!)");
97         }
98       else if (conflict == NULL)
99         {
100           return svn_error_createf
101             (SVN_ERR_FS_CONFLICT, NULL,
102              "commit conflicted as expected, "
103              "but no conflict path was returned ('%s' expected)",
104              expected_conflict);
105         }
106       else if ((strcmp(expected_conflict, "") != 0)
107                && (strcmp(conflict, expected_conflict) != 0))
108         {
109           return svn_error_createf
110             (SVN_ERR_FS_CONFLICT, NULL,
111              "commit conflicted at '%s', but expected conflict at '%s')",
112              conflict, expected_conflict);
113         }
114 
115       /* The svn_fs_commit_txn() API promises to set *NEW_REV to an
116          invalid revision number in the case of a conflict.  */
117       if (SVN_IS_VALID_REVNUM(*new_rev))
118         {
119           return svn_error_createf
120             (SVN_ERR_FS_GENERAL, NULL,
121              "conflicting commit returned valid new revision");
122         }
123     }
124   else if (err)   /* commit may have succeeded, but always report an error */
125     {
126       if (SVN_IS_VALID_REVNUM(*new_rev))
127         return svn_error_quick_wrap
128           (err, "commit succeeded but something else failed");
129       else
130         return svn_error_quick_wrap
131           (err, "commit failed due to something other than a conflict");
132     }
133   else            /* err == NULL, commit should have succeeded */
134     {
135       if (! SVN_IS_VALID_REVNUM(*new_rev))
136         {
137           return svn_error_create
138             (SVN_ERR_FS_GENERAL, NULL,
139              "commit failed but no error was returned");
140         }
141 
142       if (expected_conflict)
143         {
144           return svn_error_createf
145             (SVN_ERR_FS_GENERAL, NULL,
146              "commit succeeded that was expected to fail at '%s'",
147              expected_conflict);
148         }
149     }
150 
151   return SVN_NO_ERROR;
152 }
153 
154 
155 
156 /* Begin a txn, check its name, then close it */
157 static svn_error_t *
trivial_transaction(const svn_test_opts_t * opts,apr_pool_t * pool)158 trivial_transaction(const svn_test_opts_t *opts,
159                     apr_pool_t *pool)
160 {
161   svn_fs_t *fs;
162   svn_fs_txn_t *txn;
163   const char *txn_name;
164   int is_invalid_char[256];
165   int i;
166   const char *p;
167 
168   SVN_ERR(svn_test__create_fs(&fs, "test-repo-trivial-txn",
169                               opts, pool));
170 
171   /* Begin a new transaction that is based on revision 0.  */
172   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
173 
174   /* Test that the txn name is non-null. */
175   SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
176 
177   if (! txn_name)
178     return svn_error_create(SVN_ERR_FS_GENERAL, NULL,
179                             "Got a NULL txn name.");
180 
181   /* Test that the txn name contains only valid characters.  See
182      svn_fs.h for the list of valid characters. */
183   for (i = 0; i < sizeof(is_invalid_char)/sizeof(*is_invalid_char); ++i)
184     is_invalid_char[i] = 1;
185   for (i = '0'; i <= '9'; ++i)
186     is_invalid_char[i] = 0;
187   for (i = 'a'; i <= 'z'; ++i)
188     is_invalid_char[i] = 0;
189   for (i = 'A'; i <= 'Z'; ++i)
190     is_invalid_char[i] = 0;
191   for (p = "-."; *p; ++p)
192     is_invalid_char[(unsigned char) *p] = 0;
193 
194   for (p = txn_name; *p; ++p)
195     {
196       if (is_invalid_char[(unsigned char) *p])
197         return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
198                                  "The txn name '%s' contains an illegal '%c' "
199                                  "character", txn_name, *p);
200     }
201 
202   return SVN_NO_ERROR;
203 }
204 
205 
206 
207 /* Open an existing transaction by name. */
208 static svn_error_t *
reopen_trivial_transaction(const svn_test_opts_t * opts,apr_pool_t * pool)209 reopen_trivial_transaction(const svn_test_opts_t *opts,
210                            apr_pool_t *pool)
211 {
212   svn_fs_t *fs;
213   svn_fs_txn_t *txn;
214   svn_fs_root_t *root;
215   const char *txn_name;
216   apr_pool_t *subpool = svn_pool_create(pool);
217 
218   SVN_ERR(svn_test__create_fs(&fs, "test-repo-reopen-trivial-txn",
219                               opts, pool));
220 
221   /* Create a first transaction - we don't want that one to reopen. */
222   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
223 
224   /* Begin a second transaction that is based on revision 0.  */
225   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
226 
227   /* Don't use the subpool, txn_name must persist beyond the current txn */
228   SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
229 
230   SVN_TEST_ASSERT(svn_fs_txn_base_revision(txn) == 0);
231 
232   /* Create a third transaction - we don't want that one to reopen. */
233   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
234 
235   /* Close the transaction. */
236   svn_pool_clear(subpool);
237 
238   /* Reopen the transaction by name */
239   SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, subpool));
240 
241   /* Does it have the same name? */
242   SVN_ERR(svn_fs_txn_root(&root, txn, subpool));
243   SVN_TEST_STRING_ASSERT(svn_fs_txn_root_name(root, subpool), txn_name);
244 
245   SVN_TEST_ASSERT(svn_fs_txn_base_revision(txn) == 0);
246 
247   {
248     const char *conflict;
249     svn_revnum_t new_rev;
250     SVN_ERR(svn_fs_commit_txn(&conflict, &new_rev, txn, subpool));
251     SVN_TEST_STRING_ASSERT(conflict, NULL);
252     SVN_TEST_ASSERT(new_rev == 1);
253   }
254 
255   /* Close the transaction ... again. */
256   svn_pool_clear(subpool);
257 
258   /* Begin another transaction that is based on revision 1.  */
259   SVN_ERR(svn_fs_begin_txn(&txn, fs, 1, subpool));
260 
261   /* Don't use the subpool, txn_name must persist beyond the current txn */
262   SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
263 
264   SVN_TEST_ASSERT(svn_fs_txn_base_revision(txn) == 1);
265 
266   /* Keep the txn name in pool */
267   SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
268 
269   /* Close the transaction ... again. */
270   svn_pool_clear(subpool);
271 
272   /* Reopen the transaction by name ... again */
273   SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, subpool));
274 
275   /* Does it have the same name? ... */
276   SVN_ERR(svn_fs_txn_root(&root, txn, subpool));
277   SVN_TEST_STRING_ASSERT(svn_fs_txn_root_name(root, subpool), txn_name);
278 
279   /* And the same base revision? */
280   SVN_TEST_ASSERT(svn_fs_txn_base_revision(txn) == 1);
281 
282   svn_pool_destroy(subpool);
283 
284   return SVN_NO_ERROR;
285 }
286 
287 
288 
289 /* Create a file! */
290 static svn_error_t *
create_file_transaction(const svn_test_opts_t * opts,apr_pool_t * pool)291 create_file_transaction(const svn_test_opts_t *opts,
292                         apr_pool_t *pool)
293 {
294   svn_fs_t *fs;
295   svn_fs_txn_t *txn;
296   svn_fs_root_t *txn_root;
297 
298   SVN_ERR(svn_test__create_fs(&fs, "test-repo-create-file-txn",
299                               opts, pool));
300 
301   /* Begin a new transaction that is based on revision 0.  */
302   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
303 
304   /* Get the txn root */
305   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
306 
307   /* Create a new file in the root directory. */
308   SVN_ERR(svn_fs_make_file(txn_root, "beer.txt", pool));
309 
310   return SVN_NO_ERROR;
311 }
312 
313 
314 /* Make sure we get txn lists correctly. */
315 static svn_error_t *
verify_txn_list(const svn_test_opts_t * opts,apr_pool_t * pool)316 verify_txn_list(const svn_test_opts_t *opts,
317                 apr_pool_t *pool)
318 {
319   svn_fs_t *fs;
320   apr_pool_t *subpool;
321   svn_fs_txn_t *txn1, *txn2;
322   const char *name1, *name2;
323   apr_array_header_t *txn_list;
324 
325   SVN_ERR(svn_test__create_fs(&fs, "test-repo-verify-txn-list",
326                               opts, pool));
327 
328   /* Begin a new transaction, get its name (in the top pool), close it.  */
329   subpool = svn_pool_create(pool);
330   SVN_ERR(svn_fs_begin_txn(&txn1, fs, 0, subpool));
331   SVN_ERR(svn_fs_txn_name(&name1, txn1, pool));
332   svn_pool_destroy(subpool);
333 
334   /* Begin *another* transaction, get its name (in the top pool), close it.  */
335   subpool = svn_pool_create(pool);
336   SVN_ERR(svn_fs_begin_txn(&txn2, fs, 0, subpool));
337   SVN_ERR(svn_fs_txn_name(&name2, txn2, pool));
338   svn_pool_destroy(subpool);
339 
340   /* Get the list of active transactions from the fs. */
341   SVN_ERR(svn_fs_list_transactions(&txn_list, fs, pool));
342 
343   /* Check the list. It should have *exactly* two entries. */
344   if (txn_list->nelts != 2)
345     goto all_bad;
346 
347   /* We should be able to find our 2 txn names in the list, in some
348      order. */
349   if ((! strcmp(name1, APR_ARRAY_IDX(txn_list, 0, const char *)))
350       && (! strcmp(name2, APR_ARRAY_IDX(txn_list, 1, const char *))))
351     goto all_good;
352 
353   else if ((! strcmp(name2, APR_ARRAY_IDX(txn_list, 0, const char *)))
354            && (! strcmp(name1, APR_ARRAY_IDX(txn_list, 1, const char *))))
355     goto all_good;
356 
357  all_bad:
358 
359   return svn_error_create(SVN_ERR_FS_GENERAL, NULL,
360                           "Got a bogus txn list.");
361  all_good:
362 
363   return SVN_NO_ERROR;
364 }
365 
366 
367 /* Generate N consecutive transactions, then abort them all.  Return
368    the list of transaction names. */
369 static svn_error_t *
txn_names_are_not_reused_helper1(apr_hash_t ** txn_names,svn_fs_t * fs,apr_pool_t * pool)370 txn_names_are_not_reused_helper1(apr_hash_t **txn_names,
371                                  svn_fs_t *fs,
372                                  apr_pool_t *pool)
373 {
374   apr_hash_index_t *hi;
375   const int N = 10;
376   int i;
377   apr_pool_t *subpool = svn_pool_create(pool);
378 
379   *txn_names = apr_hash_make(pool);
380 
381   /* Create the transactions and store in a hash table the transaction
382      name as the key and the svn_fs_txn_t * as the value. */
383   for (i = 0; i < N; ++i)
384     {
385       svn_fs_txn_t *txn;
386       const char *name;
387 
388       SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
389       SVN_ERR(svn_fs_txn_name(&name, txn, pool));
390       if (apr_hash_get(*txn_names, name, APR_HASH_KEY_STRING) != NULL)
391         return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
392                                  "beginning a new transaction used an "
393                                  "existing transaction name '%s'",
394                                  name);
395       apr_hash_set(*txn_names, name, APR_HASH_KEY_STRING, txn);
396     }
397 
398   i = 0;
399   for (hi = apr_hash_first(pool, *txn_names); hi; hi = apr_hash_next(hi))
400     {
401       void *val;
402       apr_hash_this(hi, NULL, NULL, &val);
403       SVN_ERR(svn_fs_abort_txn((svn_fs_txn_t *)val, pool));
404       ++i;
405     }
406 
407   if (i != N)
408     return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
409                              "created %d transactions, but only aborted %d",
410                              N, i);
411 
412   svn_pool_destroy(subpool);
413   return SVN_NO_ERROR;
414 }
415 
416 /* Compare two hash tables and ensure that no keys in the first hash
417    table appear in the second hash table. */
418 static svn_error_t *
txn_names_are_not_reused_helper2(apr_hash_t * ht1,apr_hash_t * ht2,apr_pool_t * pool)419 txn_names_are_not_reused_helper2(apr_hash_t *ht1,
420                                  apr_hash_t *ht2,
421                                  apr_pool_t *pool)
422 {
423   apr_hash_index_t *hi;
424 
425   for (hi = apr_hash_first(pool, ht1); hi; hi = apr_hash_next(hi))
426     {
427       const void *key;
428       const char *key_string;
429       apr_hash_this(hi, &key, NULL, NULL);
430       key_string = key;
431       if (apr_hash_get(ht2, key, APR_HASH_KEY_STRING) != NULL)
432         return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
433                                  "the transaction name '%s' was reused",
434                                  key_string);
435     }
436 
437   return SVN_NO_ERROR;
438 }
439 
440 /* Make sure that transaction names are not reused. */
441 static svn_error_t *
txn_names_are_not_reused(const svn_test_opts_t * opts,apr_pool_t * pool)442 txn_names_are_not_reused(const svn_test_opts_t *opts,
443                          apr_pool_t *pool)
444 {
445   svn_fs_t *fs;
446   apr_pool_t *subpool;
447   apr_hash_t *txn_names1, *txn_names2;
448 
449   /* Bail (with success) on known-untestable scenarios */
450   if ((strcmp(opts->fs_type, "fsfs") == 0)
451       && (opts->server_minor_version && (opts->server_minor_version < 5)))
452     return SVN_NO_ERROR;
453 
454   SVN_ERR(svn_test__create_fs(&fs, "test-repo-txn-names-are-not-reused",
455                               opts, pool));
456 
457   subpool = svn_pool_create(pool);
458 
459   /* Create N transactions, abort them all, and collect the generated
460      transaction names.  Do this twice. */
461   SVN_ERR(txn_names_are_not_reused_helper1(&txn_names1, fs, subpool));
462   SVN_ERR(txn_names_are_not_reused_helper1(&txn_names2, fs, subpool));
463 
464   /* Check that no transaction names appear in both hash tables. */
465   SVN_ERR(txn_names_are_not_reused_helper2(txn_names1, txn_names2, subpool));
466   SVN_ERR(txn_names_are_not_reused_helper2(txn_names2, txn_names1, subpool));
467 
468   svn_pool_destroy(subpool);
469 
470   return SVN_NO_ERROR;
471 }
472 
473 
474 
475 /* Test writing & reading a file's contents. */
476 static svn_error_t *
write_and_read_file(const svn_test_opts_t * opts,apr_pool_t * pool)477 write_and_read_file(const svn_test_opts_t *opts,
478                     apr_pool_t *pool)
479 {
480   svn_fs_t *fs;
481   svn_fs_txn_t *txn;
482   svn_fs_root_t *txn_root;
483   svn_stream_t *rstream;
484   svn_stringbuf_t *rstring;
485   svn_stringbuf_t *wstring;
486 
487   wstring = svn_stringbuf_create("Wicki wild, wicki wicki wild.", pool);
488   SVN_ERR(svn_test__create_fs(&fs, "test-repo-read-and-write-file",
489                               opts, pool));
490   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
491   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
492 
493   /* Add an empty file. */
494   SVN_ERR(svn_fs_make_file(txn_root, "beer.txt", pool));
495 
496   /* And write some data into this file. */
497   SVN_ERR(svn_test__set_file_contents(txn_root, "beer.txt",
498                                       wstring->data, pool));
499 
500   /* Now let's read the data back from the file. */
501   SVN_ERR(svn_fs_file_contents(&rstream, txn_root, "beer.txt", pool));
502   SVN_ERR(svn_test__stream_to_string(&rstring, rstream, pool));
503 
504   /* Compare what was read to what was written. */
505   if (! svn_stringbuf_compare(rstring, wstring))
506     return svn_error_create(SVN_ERR_FS_GENERAL, NULL,
507                             "data read != data written.");
508 
509   return SVN_NO_ERROR;
510 }
511 
512 
513 
514 /* Create a file, a directory, and a file in that directory! */
515 static svn_error_t *
create_mini_tree_transaction(const svn_test_opts_t * opts,apr_pool_t * pool)516 create_mini_tree_transaction(const svn_test_opts_t *opts,
517                              apr_pool_t *pool)
518 {
519   svn_fs_t *fs;
520   svn_fs_txn_t *txn;
521   svn_fs_root_t *txn_root;
522 
523   SVN_ERR(svn_test__create_fs(&fs, "test-repo-create-mini-tree-txn",
524                               opts, pool));
525 
526   /* Begin a new transaction that is based on revision 0.  */
527   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
528 
529   /* Get the txn root */
530   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
531 
532   /* Create a new file in the root directory. */
533   SVN_ERR(svn_fs_make_file(txn_root, "wine.txt", pool));
534 
535   /* Create a new directory in the root directory. */
536   SVN_ERR(svn_fs_make_dir(txn_root, "keg", pool));
537 
538   /* Now, create a file in our new directory. */
539   SVN_ERR(svn_fs_make_file(txn_root, "keg/beer.txt", pool));
540 
541   return SVN_NO_ERROR;
542 }
543 
544 
545 /* Create a file, a directory, and a file in that directory! */
546 static svn_error_t *
create_greek_tree_transaction(const svn_test_opts_t * opts,apr_pool_t * pool)547 create_greek_tree_transaction(const svn_test_opts_t *opts,
548                               apr_pool_t *pool)
549 {
550   svn_fs_t *fs;
551   svn_fs_txn_t *txn;
552   svn_fs_root_t *txn_root;
553 
554   /* Prepare a txn to receive the greek tree. */
555   SVN_ERR(svn_test__create_fs(&fs, "test-repo-create-greek-tree-txn",
556                               opts, pool));
557   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
558   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
559 
560   /* Create and verify the greek tree. */
561   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
562 
563   return SVN_NO_ERROR;
564 }
565 
566 
567 /* Verify that entry KEY is present in ENTRIES, and that its value is
568    an svn_fs_dirent_t whose name and id are not null. */
569 static svn_error_t *
verify_entry(apr_hash_t * entries,const char * key)570 verify_entry(apr_hash_t *entries, const char *key)
571 {
572   svn_fs_dirent_t *ent = apr_hash_get(entries, key,
573                                       APR_HASH_KEY_STRING);
574 
575   if (ent == NULL)
576     return svn_error_createf
577       (SVN_ERR_FS_GENERAL, NULL,
578        "didn't find dir entry for \"%s\"", key);
579 
580   if ((ent->name == NULL) && (ent->id == NULL))
581     return svn_error_createf
582       (SVN_ERR_FS_GENERAL, NULL,
583        "dir entry for \"%s\" has null name and null id", key);
584 
585   if (ent->name == NULL)
586     return svn_error_createf
587       (SVN_ERR_FS_GENERAL, NULL,
588        "dir entry for \"%s\" has null name", key);
589 
590   if (ent->id == NULL)
591     return svn_error_createf
592       (SVN_ERR_FS_GENERAL, NULL,
593        "dir entry for \"%s\" has null id", key);
594 
595   if (strcmp(ent->name, key) != 0)
596      return svn_error_createf
597      (SVN_ERR_FS_GENERAL, NULL,
598       "dir entry for \"%s\" contains wrong name (\"%s\")", key, ent->name);
599 
600   return SVN_NO_ERROR;
601 }
602 
603 
604 static svn_error_t *
list_directory(const svn_test_opts_t * opts,apr_pool_t * pool)605 list_directory(const svn_test_opts_t *opts,
606                apr_pool_t *pool)
607 {
608   svn_fs_t *fs;
609   svn_fs_txn_t *txn;
610   svn_fs_root_t *txn_root;
611   apr_hash_t *entries;
612 
613   SVN_ERR(svn_test__create_fs(&fs, "test-repo-list-dir",
614                               opts, pool));
615   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
616   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
617 
618   /* We create this tree
619    *
620    *         /q
621    *         /A/x
622    *         /A/y
623    *         /A/z
624    *         /B/m
625    *         /B/n
626    *         /B/o
627    *
628    * then list dir A.  It should have 3 files: "x", "y", and "z", no
629    * more, no less.
630    */
631 
632   /* Create the tree. */
633   SVN_ERR(svn_fs_make_file(txn_root, "q", pool));
634   SVN_ERR(svn_fs_make_dir(txn_root, "A", pool));
635   SVN_ERR(svn_fs_make_file(txn_root, "A/x", pool));
636   SVN_ERR(svn_fs_make_file(txn_root, "A/y", pool));
637   SVN_ERR(svn_fs_make_file(txn_root, "A/z", pool));
638   SVN_ERR(svn_fs_make_dir(txn_root, "B", pool));
639   SVN_ERR(svn_fs_make_file(txn_root, "B/m", pool));
640   SVN_ERR(svn_fs_make_file(txn_root, "B/n", pool));
641   SVN_ERR(svn_fs_make_file(txn_root, "B/o", pool));
642 
643   /* Get A's entries. */
644   SVN_ERR(svn_fs_dir_entries(&entries, txn_root, "A", pool));
645 
646   /* Make sure exactly the right set of entries is present. */
647   if (apr_hash_count(entries) != 3)
648     {
649       return svn_error_create(SVN_ERR_FS_GENERAL, NULL,
650                               "unexpected number of entries in dir");
651     }
652   else
653     {
654       SVN_ERR(verify_entry(entries, "x"));
655       SVN_ERR(verify_entry(entries, "y"));
656       SVN_ERR(verify_entry(entries, "z"));
657     }
658 
659   return SVN_NO_ERROR;
660 }
661 
662 
663 /* If EXPR raises SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, continue; else, fail
664  * the test. */
665 #define FAILS_WITH_BOV(expr) \
666   do { \
667       svn_error_t *__err = (expr); \
668       if (!__err || __err->apr_err != SVN_ERR_FS_PROP_BASEVALUE_MISMATCH) \
669         return svn_error_create(SVN_ERR_TEST_FAILED, __err, \
670                                 "svn_fs_change_rev_prop2() failed to " \
671                                 "detect unexpected old value"); \
672       else \
673         svn_error_clear(__err); \
674   } while (0)
675 
676 static svn_error_t *
revision_props(const svn_test_opts_t * opts,apr_pool_t * pool)677 revision_props(const svn_test_opts_t *opts,
678                apr_pool_t *pool)
679 {
680   svn_fs_t *fs;
681   apr_hash_t *proplist;
682   svn_string_t *value;
683   int i;
684   svn_string_t s1;
685 
686   const char *initial_props[4][2] = {
687     { "color", "red" },
688     { "size", "XXL" },
689     { "favorite saturday morning cartoon", "looney tunes" },
690     { "auto", "Green 1997 Saturn SL1" }
691     };
692 
693   const char *final_props[4][2] = {
694     { "color", "violet" },
695     { "flower", "violet" },
696     { "favorite saturday morning cartoon", "looney tunes" },
697     { "auto", "Red 2000 Chevrolet Blazer" }
698     };
699 
700   /* Open the fs */
701   SVN_ERR(svn_test__create_fs(&fs, "test-repo-rev-props",
702                               opts, pool));
703 
704   /* Set some properties on the revision. */
705   for (i = 0; i < 4; i++)
706     {
707       SET_STR(&s1, initial_props[i][1]);
708       SVN_ERR(svn_fs_change_rev_prop(fs, 0, initial_props[i][0], &s1, pool));
709     }
710 
711   /* Change some of the above properties. */
712   SET_STR(&s1, "violet");
713   SVN_ERR(svn_fs_change_rev_prop(fs, 0, "color", &s1, pool));
714 
715   SET_STR(&s1, "Red 2000 Chevrolet Blazer");
716   SVN_ERR(svn_fs_change_rev_prop(fs, 0, "auto", &s1, pool));
717 
718   /* Remove a property altogether */
719   SVN_ERR(svn_fs_change_rev_prop(fs, 0, "size", NULL, pool));
720 
721   /* Copy a property's value into a new property. */
722   SVN_ERR(svn_fs_revision_prop(&value, fs, 0, "color", pool));
723   SVN_TEST_ASSERT(value);
724 
725   s1.data = value->data;
726   s1.len = value->len;
727   SVN_ERR(svn_fs_change_rev_prop(fs, 0, "flower", &s1, pool));
728 
729   /* Test svn_fs_change_rev_prop2().  If the whole block goes through, then
730    * it is a no-op (it undoes all changes it makes). */
731     {
732       const svn_string_t s2 = { "wrong value", 11 };
733       const svn_string_t *s2_p = &s2;
734       const svn_string_t *s1_p = &s1;
735       const svn_string_t *unset = NULL;
736       const svn_string_t *s1_dup;
737 
738       /* Value of "flower" is 's1'. */
739 
740       FAILS_WITH_BOV(svn_fs_change_rev_prop2(fs, 0, "flower", &s2_p, s1_p, pool));
741       s1_dup = svn_string_dup(&s1, pool);
742       SVN_ERR(svn_fs_change_rev_prop2(fs, 0, "flower", &s1_dup, s2_p, pool));
743 
744       /* Value of "flower" is 's2'. */
745 
746       FAILS_WITH_BOV(svn_fs_change_rev_prop2(fs, 0, "flower", &s1_p, NULL, pool));
747       SVN_ERR(svn_fs_change_rev_prop2(fs, 0, "flower", &s2_p, NULL, pool));
748 
749       /* Value of "flower" is <not set>. */
750 
751       FAILS_WITH_BOV(svn_fs_change_rev_prop2(fs, 0, "flower", &s2_p, s1_p, pool));
752       SVN_ERR(svn_fs_change_rev_prop2(fs, 0, "flower", &unset, s1_p, pool));
753 
754       /* Value of "flower" is 's1'. */
755     }
756 
757   /* Obtain a list of all current properties, and make sure it matches
758      the expected values. */
759   SVN_ERR(svn_fs_revision_proplist(&proplist, fs, 0, pool));
760   SVN_TEST_ASSERT(proplist);
761   {
762     svn_string_t *prop_value;
763 
764     if (apr_hash_count(proplist) < 4 )
765       return svn_error_createf
766         (SVN_ERR_FS_GENERAL, NULL,
767          "too few revision properties found");
768 
769     /* Loop through our list of expected revision property name/value
770        pairs. */
771     for (i = 0; i < 4; i++)
772       {
773         /* For each expected property: */
774 
775         /* Step 1.  Find it by name in the hash of all rev. props
776            returned to us by svn_fs_revision_proplist.  If it can't be
777            found, return an error. */
778         prop_value = apr_hash_get(proplist,
779                                   final_props[i][0],
780                                   APR_HASH_KEY_STRING);
781         if (! prop_value)
782           return svn_error_createf
783             (SVN_ERR_FS_GENERAL, NULL,
784              "unable to find expected revision property");
785 
786         /* Step 2.  Make sure the value associated with it is the same
787            as what was expected, else return an error. */
788         if (strcmp(prop_value->data, final_props[i][1]))
789           return svn_error_createf
790             (SVN_ERR_FS_GENERAL, NULL,
791              "revision property had an unexpected value");
792       }
793   }
794 
795   return SVN_NO_ERROR;
796 }
797 
798 
799 static svn_error_t *
transaction_props(const svn_test_opts_t * opts,apr_pool_t * pool)800 transaction_props(const svn_test_opts_t *opts,
801                   apr_pool_t *pool)
802 {
803   svn_fs_t *fs;
804   svn_fs_txn_t *txn;
805   apr_hash_t *proplist;
806   svn_string_t *value;
807   svn_revnum_t after_rev;
808   int i;
809   svn_string_t s1;
810 
811   const char *initial_props[4][2] = {
812     { "color", "red" },
813     { "size", "XXL" },
814     { "favorite saturday morning cartoon", "looney tunes" },
815     { "auto", "Green 1997 Saturn SL1" }
816     };
817 
818   const char *final_props[5][2] = {
819     { "color", "violet" },
820     { "flower", "violet" },
821     { "favorite saturday morning cartoon", "looney tunes" },
822     { "auto", "Red 2000 Chevrolet Blazer" },
823     { SVN_PROP_REVISION_DATE, "<some datestamp value>" }
824     };
825 
826   /* Open the fs */
827   SVN_ERR(svn_test__create_fs(&fs, "test-repo-txn-props",
828                               opts, pool));
829   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
830 
831   /* Set some properties on the revision. */
832   for (i = 0; i < 4; i++)
833     {
834       SET_STR(&s1, initial_props[i][1]);
835       SVN_ERR(svn_fs_change_txn_prop(txn, initial_props[i][0], &s1, pool));
836     }
837 
838   /* Change some of the above properties. */
839   SET_STR(&s1, "violet");
840   SVN_ERR(svn_fs_change_txn_prop(txn, "color", &s1, pool));
841 
842   SET_STR(&s1, "Red 2000 Chevrolet Blazer");
843   SVN_ERR(svn_fs_change_txn_prop(txn, "auto", &s1, pool));
844 
845   /* Remove a property altogether */
846   SVN_ERR(svn_fs_change_txn_prop(txn, "size", NULL, pool));
847 
848   /* Copy a property's value into a new property. */
849   SVN_ERR(svn_fs_txn_prop(&value, txn, "color", pool));
850 
851   s1.data = value->data;
852   s1.len = value->len;
853   SVN_ERR(svn_fs_change_txn_prop(txn, "flower", &s1, pool));
854 
855   /* Obtain a list of all current properties, and make sure it matches
856      the expected values. */
857   SVN_ERR(svn_fs_txn_proplist(&proplist, txn, pool));
858   {
859     svn_string_t *prop_value;
860 
861     /* All transactions get a datestamp property at their inception,
862        so we expect *5*, not 4 properties. */
863     if (apr_hash_count(proplist) != 5 )
864       return svn_error_createf
865         (SVN_ERR_FS_GENERAL, NULL,
866          "unexpected number of transaction properties were found");
867 
868     /* Loop through our list of expected revision property name/value
869        pairs. */
870     for (i = 0; i < 5; i++)
871       {
872         /* For each expected property: */
873 
874         /* Step 1.  Find it by name in the hash of all rev. props
875            returned to us by svn_fs_revision_proplist.  If it can't be
876            found, return an error. */
877         prop_value = apr_hash_get(proplist,
878                                   final_props[i][0],
879                                   APR_HASH_KEY_STRING);
880         if (! prop_value)
881           return svn_error_createf
882             (SVN_ERR_FS_GENERAL, NULL,
883              "unable to find expected transaction property");
884 
885         /* Step 2.  Make sure the value associated with it is the same
886            as what was expected, else return an error. */
887         if (strcmp(final_props[i][0], SVN_PROP_REVISION_DATE))
888           if (strcmp(prop_value->data, final_props[i][1]))
889             return svn_error_createf
890               (SVN_ERR_FS_GENERAL, NULL,
891                "transaction property had an unexpected value");
892       }
893   }
894 
895   /* Commit the transaction. */
896   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
897   if (after_rev != 1)
898     return svn_error_createf
899       (SVN_ERR_FS_GENERAL, NULL,
900        "committed transaction got wrong revision number");
901 
902   /* Obtain a list of all properties on the new revision, and make
903      sure it matches the expected values.  If you're wondering, the
904      expected values should be the exact same set of properties that
905      existed on the transaction just prior to its being committed. */
906   SVN_ERR(svn_fs_revision_proplist(&proplist, fs, after_rev, pool));
907   {
908     svn_string_t *prop_value;
909 
910     if (apr_hash_count(proplist) < 5 )
911       return svn_error_createf
912         (SVN_ERR_FS_GENERAL, NULL,
913          "unexpected number of revision properties were found");
914 
915     /* Loop through our list of expected revision property name/value
916        pairs. */
917     for (i = 0; i < 5; i++)
918       {
919         /* For each expected property: */
920 
921         /* Step 1.  Find it by name in the hash of all rev. props
922            returned to us by svn_fs_revision_proplist.  If it can't be
923            found, return an error. */
924         prop_value = apr_hash_get(proplist,
925                                   final_props[i][0],
926                                   APR_HASH_KEY_STRING);
927         if (! prop_value)
928           return svn_error_createf
929             (SVN_ERR_FS_GENERAL, NULL,
930              "unable to find expected revision property");
931 
932         /* Step 2.  Make sure the value associated with it is the same
933            as what was expected, else return an error. */
934         if (strcmp(final_props[i][0], SVN_PROP_REVISION_DATE))
935           if (strcmp(prop_value->data, final_props[i][1]))
936             return svn_error_createf
937               (SVN_ERR_FS_GENERAL, NULL,
938                "revision property had an unexpected value");
939       }
940   }
941 
942   return SVN_NO_ERROR;
943 }
944 
945 
946 static svn_error_t *
node_props(const svn_test_opts_t * opts,apr_pool_t * pool)947 node_props(const svn_test_opts_t *opts,
948            apr_pool_t *pool)
949 {
950   svn_fs_t *fs;
951   svn_fs_txn_t *txn;
952   svn_fs_root_t *txn_root;
953   apr_hash_t *proplist;
954   svn_string_t *value;
955   int i;
956   svn_string_t s1;
957 
958   const char *initial_props[4][2] = {
959     { "Best Rock Artist", "Creed" },
960     { "Best Rap Artist", "Eminem" },
961     { "Best Country Artist", "(null)" },
962     { "Best Sound Designer", "Pluessman" }
963     };
964 
965   const char *final_props[4][2] = {
966     { "Best Rock Artist", "P.O.D." },
967     { "Best Rap Artist", "Busta Rhymes" },
968     { "Best Sound Designer", "Pluessman" },
969     { "Biggest Cakewalk Fanatic", "Pluessman" }
970     };
971 
972   /* Open the fs and transaction */
973   SVN_ERR(svn_test__create_fs(&fs, "test-repo-node-props",
974                               opts, pool));
975   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
976   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
977 
978   /* Make a node to put some properties into */
979   SVN_ERR(svn_fs_make_file(txn_root, "music.txt", pool));
980 
981   /* Set some properties on the nodes. */
982   for (i = 0; i < 4; i++)
983     {
984       SET_STR(&s1, initial_props[i][1]);
985       SVN_ERR(svn_fs_change_node_prop
986               (txn_root, "music.txt", initial_props[i][0], &s1, pool));
987     }
988 
989   /* Change some of the above properties. */
990   SET_STR(&s1, "P.O.D.");
991   SVN_ERR(svn_fs_change_node_prop(txn_root, "music.txt", "Best Rock Artist",
992                                   &s1, pool));
993 
994   SET_STR(&s1, "Busta Rhymes");
995   SVN_ERR(svn_fs_change_node_prop(txn_root, "music.txt", "Best Rap Artist",
996                                   &s1, pool));
997 
998   /* Remove a property altogether */
999   SVN_ERR(svn_fs_change_node_prop(txn_root, "music.txt",
1000                                   "Best Country Artist", NULL, pool));
1001 
1002   /* Copy a property's value into a new property. */
1003   SVN_ERR(svn_fs_node_prop(&value, txn_root, "music.txt",
1004                            "Best Sound Designer", pool));
1005 
1006   s1.data = value->data;
1007   s1.len = value->len;
1008   SVN_ERR(svn_fs_change_node_prop(txn_root, "music.txt",
1009                                   "Biggest Cakewalk Fanatic", &s1, pool));
1010 
1011   /* Obtain a list of all current properties, and make sure it matches
1012      the expected values. */
1013   SVN_ERR(svn_fs_node_proplist(&proplist, txn_root, "music.txt", pool));
1014   {
1015     svn_string_t *prop_value;
1016 
1017     if (apr_hash_count(proplist) != 4 )
1018       return svn_error_createf
1019         (SVN_ERR_FS_GENERAL, NULL,
1020          "unexpected number of node properties were found");
1021 
1022     /* Loop through our list of expected node property name/value
1023        pairs. */
1024     for (i = 0; i < 4; i++)
1025       {
1026         /* For each expected property: */
1027 
1028         /* Step 1.  Find it by name in the hash of all node props
1029            returned to us by svn_fs_node_proplist.  If it can't be
1030            found, return an error. */
1031         prop_value = apr_hash_get(proplist,
1032                                   final_props[i][0],
1033                                   APR_HASH_KEY_STRING);
1034         if (! prop_value)
1035           return svn_error_createf
1036             (SVN_ERR_FS_GENERAL, NULL,
1037              "unable to find expected node property");
1038 
1039         /* Step 2.  Make sure the value associated with it is the same
1040            as what was expected, else return an error. */
1041         if (strcmp(prop_value->data, final_props[i][1]))
1042           return svn_error_createf
1043             (SVN_ERR_FS_GENERAL, NULL,
1044              "node property had an unexpected value");
1045       }
1046   }
1047 
1048   return SVN_NO_ERROR;
1049 }
1050 
1051 
1052 
1053 /* Set *PRESENT to true if entry NAME is present in directory PATH
1054    under ROOT, else set *PRESENT to false. */
1055 static svn_error_t *
check_entry(svn_fs_root_t * root,const char * path,const char * name,svn_boolean_t * present,apr_pool_t * pool)1056 check_entry(svn_fs_root_t *root,
1057             const char *path,
1058             const char *name,
1059             svn_boolean_t *present,
1060             apr_pool_t *pool)
1061 {
1062   apr_hash_t *entries;
1063   svn_fs_dirent_t *ent;
1064   apr_pool_t *subpool = svn_pool_create(pool);
1065 
1066   SVN_ERR(svn_fs_dir_entries(&entries, root, path, subpool));
1067   ent = apr_hash_get(entries, name, APR_HASH_KEY_STRING);
1068 
1069   if (ent)
1070     *present = TRUE;
1071   else
1072     *present = FALSE;
1073 
1074   svn_pool_destroy(subpool);
1075   return SVN_NO_ERROR;
1076 }
1077 
1078 
1079 /* Return an error if entry NAME is absent in directory PATH under ROOT. */
1080 static svn_error_t *
check_entry_present(svn_fs_root_t * root,const char * path,const char * name,apr_pool_t * pool)1081 check_entry_present(svn_fs_root_t *root, const char *path,
1082                     const char *name, apr_pool_t *pool)
1083 {
1084   svn_boolean_t present = FALSE;
1085   SVN_ERR(check_entry(root, path, name, &present, pool));
1086 
1087   if (! present)
1088     return svn_error_createf
1089       (SVN_ERR_FS_GENERAL, NULL,
1090        "entry \"%s\" absent when it should be present", name);
1091 
1092   return SVN_NO_ERROR;
1093 }
1094 
1095 
1096 /* Return an error if entry NAME is present in directory PATH under ROOT. */
1097 static svn_error_t *
check_entry_absent(svn_fs_root_t * root,const char * path,const char * name,apr_pool_t * pool)1098 check_entry_absent(svn_fs_root_t *root, const char *path,
1099                    const char *name, apr_pool_t *pool)
1100 {
1101   svn_boolean_t present = TRUE;
1102   SVN_ERR(check_entry(root, path, name, &present, pool));
1103 
1104   if (present)
1105     return svn_error_createf
1106       (SVN_ERR_FS_GENERAL, NULL,
1107        "entry \"%s\" present when it should be absent", name);
1108 
1109   return SVN_NO_ERROR;
1110 }
1111 
1112 
1113 /* Fetch the youngest revision from a repos. */
1114 static svn_error_t *
fetch_youngest_rev(const svn_test_opts_t * opts,apr_pool_t * pool)1115 fetch_youngest_rev(const svn_test_opts_t *opts,
1116                    apr_pool_t *pool)
1117 {
1118   svn_fs_t *fs;
1119   svn_fs_txn_t *txn;
1120   svn_fs_root_t *txn_root;
1121   svn_revnum_t new_rev;
1122   svn_revnum_t youngest_rev, new_youngest_rev;
1123 
1124   SVN_ERR(svn_test__create_fs(&fs, "test-repo-youngest-rev",
1125                               opts, pool));
1126 
1127   /* Get youngest revision of brand spankin' new filesystem. */
1128   SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
1129 
1130   /* Prepare a txn to receive the greek tree. */
1131   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
1132   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1133 
1134   /* Create the greek tree. */
1135   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
1136 
1137   /* Commit it. */
1138   SVN_ERR(test_commit_txn(&new_rev, txn, NULL, pool));
1139 
1140   /* Get the new youngest revision. */
1141   SVN_ERR(svn_fs_youngest_rev(&new_youngest_rev, fs, pool));
1142 
1143   if (youngest_rev == new_rev)
1144     return svn_error_create(SVN_ERR_FS_GENERAL, NULL,
1145                             "commit didn't bump up revision number");
1146 
1147   if (new_youngest_rev != new_rev)
1148     return svn_error_create(SVN_ERR_FS_GENERAL, NULL,
1149                             "couldn't fetch youngest revision");
1150 
1151   return SVN_NO_ERROR;
1152 }
1153 
1154 
1155 /* Test committing against an empty repository.
1156    todo: also test committing against youngest? */
1157 static svn_error_t *
basic_commit(const svn_test_opts_t * opts,apr_pool_t * pool)1158 basic_commit(const svn_test_opts_t *opts,
1159              apr_pool_t *pool)
1160 {
1161   svn_fs_t *fs;
1162   svn_fs_txn_t *txn;
1163   svn_fs_root_t *txn_root, *revision_root;
1164   svn_revnum_t before_rev, after_rev;
1165   const char *conflict;
1166 
1167   /* Prepare a filesystem. */
1168   SVN_ERR(svn_test__create_fs(&fs, "test-repo-basic-commit",
1169                               opts, pool));
1170 
1171   /* Save the current youngest revision. */
1172   SVN_ERR(svn_fs_youngest_rev(&before_rev, fs, pool));
1173 
1174   /* Prepare a txn to receive the greek tree. */
1175   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
1176   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1177 
1178   /* Paranoidly check that the current youngest rev is unchanged. */
1179   SVN_ERR(svn_fs_youngest_rev(&after_rev, fs, pool));
1180   if (after_rev != before_rev)
1181     return svn_error_create
1182       (SVN_ERR_FS_GENERAL, NULL,
1183        "youngest revision changed unexpectedly");
1184 
1185   /* Create the greek tree. */
1186   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
1187   SVN_TEST_ASSERT(svn_fs_is_txn_root(txn_root));
1188   SVN_TEST_ASSERT(!svn_fs_is_revision_root(txn_root));
1189 
1190   /* Commit it. */
1191   SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, pool));
1192   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(after_rev));
1193 
1194   /* Make sure it's a different revision than before. */
1195   if (after_rev == before_rev)
1196     return svn_error_create
1197       (SVN_ERR_FS_GENERAL, NULL,
1198        "youngest revision failed to change");
1199 
1200   /* Get root of the revision */
1201   SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool));
1202   SVN_TEST_ASSERT(!svn_fs_is_txn_root(revision_root));
1203   SVN_TEST_ASSERT(svn_fs_is_revision_root(revision_root));
1204 
1205   /* Check the tree. */
1206   SVN_ERR(svn_test__check_greek_tree(revision_root, pool));
1207 
1208   return SVN_NO_ERROR;
1209 }
1210 
1211 
1212 
1213 static svn_error_t *
test_tree_node_validation(const svn_test_opts_t * opts,apr_pool_t * pool)1214 test_tree_node_validation(const svn_test_opts_t *opts,
1215                           apr_pool_t *pool)
1216 {
1217   svn_fs_t *fs;
1218   svn_fs_txn_t *txn;
1219   svn_fs_root_t *txn_root, *revision_root;
1220   svn_revnum_t after_rev;
1221   const char *conflict;
1222   apr_pool_t *subpool;
1223 
1224   /* Prepare a filesystem. */
1225   SVN_ERR(svn_test__create_fs(&fs, "test-repo-validate-tree-entries",
1226                               opts, pool));
1227 
1228   /* In a txn, create the greek tree. */
1229   subpool = svn_pool_create(pool);
1230   {
1231     static svn_test__tree_entry_t expected_entries[] = {
1232       /* path, contents (0 = dir) */
1233       { "iota",        "This is the file 'iota'.\n" },
1234       { "A",           0 },
1235       { "A/mu",        "This is the file 'mu'.\n" },
1236       { "A/B",         0 },
1237       { "A/B/lambda",  "This is the file 'lambda'.\n" },
1238       { "A/B/E",       0 },
1239       { "A/B/E/alpha", "This is the file 'alpha'.\n" },
1240       { "A/B/E/beta",  "This is the file 'beta'.\n" },
1241       { "A/B/F",       0 },
1242       { "A/C",         0 },
1243       { "A/D",         0 },
1244       { "A/D/gamma",   "This is the file 'gamma'.\n" },
1245       { "A/D/G",       0 },
1246       { "A/D/G/pi",    "This is the file 'pi'.\n" },
1247       { "A/D/G/rho",   "This is the file 'rho'.\n" },
1248       { "A/D/G/tau",   "This is the file 'tau'.\n" },
1249       { "A/D/H",       0 },
1250       { "A/D/H/chi",   "This is the file 'chi'.\n" },
1251       { "A/D/H/psi",   "This is the file 'psi'.\n" },
1252       { "A/D/H/omega", "This is the file 'omega'.\n" }
1253     };
1254     SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
1255     SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
1256     SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
1257 
1258     /* Carefully validate that tree in the transaction. */
1259     SVN_ERR(svn_test__validate_tree(txn_root, expected_entries, 20,
1260                                     subpool));
1261 
1262     /* Go ahead and commit the tree, and destroy the txn object.  */
1263     SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, subpool));
1264     SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(after_rev));
1265 
1266     /* Carefully validate that tree in the new revision, now. */
1267     SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, subpool));
1268     SVN_ERR(svn_test__validate_tree(revision_root, expected_entries, 20,
1269                                     subpool));
1270   }
1271   svn_pool_destroy(subpool);
1272 
1273   /* In a new txn, modify the greek tree. */
1274   subpool = svn_pool_create(pool);
1275   {
1276     static svn_test__tree_entry_t expected_entries[] = {
1277       /* path, contents (0 = dir) */
1278       { "iota",          "This is a new version of 'iota'.\n" },
1279       { "A",             0 },
1280       { "A/B",           0 },
1281       { "A/B/lambda",    "This is the file 'lambda'.\n" },
1282       { "A/B/E",         0 },
1283       { "A/B/E/alpha",   "This is the file 'alpha'.\n" },
1284       { "A/B/E/beta",    "This is the file 'beta'.\n" },
1285       { "A/B/F",         0 },
1286       { "A/C",           0 },
1287       { "A/C/kappa",     "This is the file 'kappa'.\n" },
1288       { "A/D",           0 },
1289       { "A/D/gamma",     "This is the file 'gamma'.\n" },
1290       { "A/D/H",         0 },
1291       { "A/D/H/chi",     "This is the file 'chi'.\n" },
1292       { "A/D/H/psi",     "This is the file 'psi'.\n" },
1293       { "A/D/H/omega",   "This is the file 'omega'.\n" },
1294       { "A/D/I",         0 },
1295       { "A/D/I/delta",   "This is the file 'delta'.\n" },
1296       { "A/D/I/epsilon", "This is the file 'epsilon'.\n" }
1297     };
1298 
1299     SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, subpool));
1300     SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
1301     SVN_ERR(svn_test__set_file_contents
1302             (txn_root, "iota", "This is a new version of 'iota'.\n",
1303              subpool));
1304     SVN_ERR(svn_fs_delete(txn_root, "A/mu", subpool));
1305     SVN_ERR(svn_fs_delete(txn_root, "A/D/G", subpool));
1306     SVN_ERR(svn_fs_make_dir(txn_root, "A/D/I", subpool));
1307     SVN_ERR(svn_fs_make_file(txn_root, "A/D/I/delta", subpool));
1308     SVN_ERR(svn_test__set_file_contents
1309             (txn_root, "A/D/I/delta", "This is the file 'delta'.\n",
1310              subpool));
1311     SVN_ERR(svn_fs_make_file(txn_root, "A/D/I/epsilon", subpool));
1312     SVN_ERR(svn_test__set_file_contents
1313             (txn_root, "A/D/I/epsilon", "This is the file 'epsilon'.\n",
1314              subpool));
1315     SVN_ERR(svn_fs_make_file(txn_root, "A/C/kappa", subpool));
1316     SVN_ERR(svn_test__set_file_contents
1317             (txn_root, "A/C/kappa", "This is the file 'kappa'.\n",
1318              subpool));
1319 
1320     /* Carefully validate that tree in the transaction. */
1321     SVN_ERR(svn_test__validate_tree(txn_root, expected_entries, 19,
1322                                     subpool));
1323 
1324     /* Go ahead and commit the tree, and destroy the txn object.  */
1325     SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, subpool));
1326     SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(after_rev));
1327 
1328     /* Carefully validate that tree in the new revision, now. */
1329     SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, subpool));
1330     SVN_ERR(svn_test__validate_tree(revision_root, expected_entries,
1331                                     19, subpool));
1332   }
1333   svn_pool_destroy(subpool);
1334 
1335   return SVN_NO_ERROR;
1336 }
1337 
1338 
1339 /* Commit with merging (committing against non-youngest). */
1340 static svn_error_t *
merging_commit(const svn_test_opts_t * opts,apr_pool_t * pool)1341 merging_commit(const svn_test_opts_t *opts,
1342                apr_pool_t *pool)
1343 {
1344   svn_fs_t *fs;
1345   svn_fs_txn_t *txn;
1346   svn_fs_root_t *txn_root, *revision_root;
1347   svn_revnum_t after_rev;
1348   svn_revnum_t revisions[24];
1349   apr_size_t i;
1350   svn_revnum_t revision_count;
1351 
1352   /* Prepare a filesystem. */
1353   SVN_ERR(svn_test__create_fs(&fs, "test-repo-merging-commit",
1354                               opts, pool));
1355 
1356   /* Initialize our revision number stuffs. */
1357   for (i = 0;
1358        i < ((sizeof(revisions)) / (sizeof(svn_revnum_t)));
1359        i++)
1360     revisions[i] = SVN_INVALID_REVNUM;
1361   revision_count = 0;
1362   revisions[revision_count++] = 0; /* the brand spankin' new revision */
1363 
1364   /***********************************************************************/
1365   /* REVISION 0 */
1366   /***********************************************************************/
1367 
1368   /* In one txn, create and commit the greek tree. */
1369   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
1370   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1371   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
1372   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
1373 
1374   /***********************************************************************/
1375   /* REVISION 1 */
1376   /***********************************************************************/
1377   {
1378     static svn_test__tree_entry_t expected_entries[] = {
1379       /* path, contents (0 = dir) */
1380       { "iota",        "This is the file 'iota'.\n" },
1381       { "A",           0 },
1382       { "A/mu",        "This is the file 'mu'.\n" },
1383       { "A/B",         0 },
1384       { "A/B/lambda",  "This is the file 'lambda'.\n" },
1385       { "A/B/E",       0 },
1386       { "A/B/E/alpha", "This is the file 'alpha'.\n" },
1387       { "A/B/E/beta",  "This is the file 'beta'.\n" },
1388       { "A/B/F",       0 },
1389       { "A/C",         0 },
1390       { "A/D",         0 },
1391       { "A/D/gamma",   "This is the file 'gamma'.\n" },
1392       { "A/D/G",       0 },
1393       { "A/D/G/pi",    "This is the file 'pi'.\n" },
1394       { "A/D/G/rho",   "This is the file 'rho'.\n" },
1395       { "A/D/G/tau",   "This is the file 'tau'.\n" },
1396       { "A/D/H",       0 },
1397       { "A/D/H/chi",   "This is the file 'chi'.\n" },
1398       { "A/D/H/psi",   "This is the file 'psi'.\n" },
1399       { "A/D/H/omega", "This is the file 'omega'.\n" }
1400     };
1401     SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool));
1402     SVN_ERR(svn_test__validate_tree(revision_root, expected_entries,
1403                                     20, pool));
1404   }
1405   revisions[revision_count++] = after_rev;
1406 
1407   /* Let's add a directory and some files to the tree, and delete
1408      'iota' */
1409   SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[revision_count-1], pool));
1410   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1411   SVN_ERR(svn_fs_make_dir(txn_root, "A/D/I", pool));
1412   SVN_ERR(svn_fs_make_file(txn_root, "A/D/I/delta", pool));
1413   SVN_ERR(svn_test__set_file_contents
1414           (txn_root, "A/D/I/delta", "This is the file 'delta'.\n", pool));
1415   SVN_ERR(svn_fs_make_file(txn_root, "A/D/I/epsilon", pool));
1416   SVN_ERR(svn_test__set_file_contents
1417           (txn_root, "A/D/I/epsilon", "This is the file 'epsilon'.\n", pool));
1418   SVN_ERR(svn_fs_make_file(txn_root, "A/C/kappa", pool));
1419   SVN_ERR(svn_test__set_file_contents
1420           (txn_root, "A/C/kappa", "This is the file 'kappa'.\n", pool));
1421   SVN_ERR(svn_fs_delete(txn_root, "iota", pool));
1422   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
1423 
1424   /***********************************************************************/
1425   /* REVISION 2 */
1426   /***********************************************************************/
1427   {
1428     static svn_test__tree_entry_t expected_entries[] = {
1429       /* path, contents (0 = dir) */
1430       { "A",             0 },
1431       { "A/mu",          "This is the file 'mu'.\n" },
1432       { "A/B",           0 },
1433       { "A/B/lambda",    "This is the file 'lambda'.\n" },
1434       { "A/B/E",         0 },
1435       { "A/B/E/alpha",   "This is the file 'alpha'.\n" },
1436       { "A/B/E/beta",    "This is the file 'beta'.\n" },
1437       { "A/B/F",         0 },
1438       { "A/C",           0 },
1439       { "A/C/kappa",     "This is the file 'kappa'.\n" },
1440       { "A/D",           0 },
1441       { "A/D/gamma",     "This is the file 'gamma'.\n" },
1442       { "A/D/G",         0 },
1443       { "A/D/G/pi",      "This is the file 'pi'.\n" },
1444       { "A/D/G/rho",     "This is the file 'rho'.\n" },
1445       { "A/D/G/tau",     "This is the file 'tau'.\n" },
1446       { "A/D/H",         0 },
1447       { "A/D/H/chi",     "This is the file 'chi'.\n" },
1448       { "A/D/H/psi",     "This is the file 'psi'.\n" },
1449       { "A/D/H/omega",   "This is the file 'omega'.\n" },
1450       { "A/D/I",         0 },
1451       { "A/D/I/delta",   "This is the file 'delta'.\n" },
1452       { "A/D/I/epsilon", "This is the file 'epsilon'.\n" }
1453     };
1454     SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool));
1455     SVN_ERR(svn_test__validate_tree(revision_root, expected_entries,
1456                                     23, pool));
1457   }
1458   revisions[revision_count++] = after_rev;
1459 
1460   /* We don't think the A/D/H directory is pulling its weight...let's
1461      knock it off.  Oh, and let's re-add iota, too. */
1462   SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[revision_count-1], pool));
1463   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1464   SVN_ERR(svn_fs_delete(txn_root, "A/D/H", pool));
1465   SVN_ERR(svn_fs_make_file(txn_root, "iota", pool));
1466   SVN_ERR(svn_test__set_file_contents
1467           (txn_root, "iota", "This is the new file 'iota'.\n", pool));
1468   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
1469 
1470   /***********************************************************************/
1471   /* REVISION 3 */
1472   /***********************************************************************/
1473   {
1474     static svn_test__tree_entry_t expected_entries[] = {
1475       /* path, contents (0 = dir) */
1476       { "iota",          "This is the new file 'iota'.\n" },
1477       { "A",             0 },
1478       { "A/mu",          "This is the file 'mu'.\n" },
1479       { "A/B",           0 },
1480       { "A/B/lambda",    "This is the file 'lambda'.\n" },
1481       { "A/B/E",         0 },
1482       { "A/B/E/alpha",   "This is the file 'alpha'.\n" },
1483       { "A/B/E/beta",    "This is the file 'beta'.\n" },
1484       { "A/B/F",         0 },
1485       { "A/C",           0 },
1486       { "A/C/kappa",     "This is the file 'kappa'.\n" },
1487       { "A/D",           0 },
1488       { "A/D/gamma",     "This is the file 'gamma'.\n" },
1489       { "A/D/G",         0 },
1490       { "A/D/G/pi",      "This is the file 'pi'.\n" },
1491       { "A/D/G/rho",     "This is the file 'rho'.\n" },
1492       { "A/D/G/tau",     "This is the file 'tau'.\n" },
1493       { "A/D/I",         0 },
1494       { "A/D/I/delta",   "This is the file 'delta'.\n" },
1495       { "A/D/I/epsilon", "This is the file 'epsilon'.\n" }
1496     };
1497     SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool));
1498     SVN_ERR(svn_test__validate_tree(revision_root, expected_entries,
1499                                     20, pool));
1500   }
1501   revisions[revision_count++] = after_rev;
1502 
1503   /* Delete iota (yet again). */
1504   SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[revision_count-1], pool));
1505   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1506   SVN_ERR(svn_fs_delete(txn_root, "iota", pool));
1507   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
1508 
1509   /***********************************************************************/
1510   /* REVISION 4 */
1511   /***********************************************************************/
1512   {
1513     static svn_test__tree_entry_t expected_entries[] = {
1514       /* path, contents (0 = dir) */
1515       { "A",             0 },
1516       { "A/mu",          "This is the file 'mu'.\n" },
1517       { "A/B",           0 },
1518       { "A/B/lambda",    "This is the file 'lambda'.\n" },
1519       { "A/B/E",         0 },
1520       { "A/B/E/alpha",   "This is the file 'alpha'.\n" },
1521       { "A/B/E/beta",    "This is the file 'beta'.\n" },
1522       { "A/B/F",         0 },
1523       { "A/C",           0 },
1524       { "A/C/kappa",     "This is the file 'kappa'.\n" },
1525       { "A/D",           0 },
1526       { "A/D/gamma",     "This is the file 'gamma'.\n" },
1527       { "A/D/G",         0 },
1528       { "A/D/G/pi",      "This is the file 'pi'.\n" },
1529       { "A/D/G/rho",     "This is the file 'rho'.\n" },
1530       { "A/D/G/tau",     "This is the file 'tau'.\n" },
1531       { "A/D/I",         0 },
1532       { "A/D/I/delta",   "This is the file 'delta'.\n" },
1533       { "A/D/I/epsilon", "This is the file 'epsilon'.\n" }
1534     };
1535     SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool));
1536     SVN_ERR(svn_test__validate_tree(revision_root, expected_entries,
1537                                     19, pool));
1538   }
1539   revisions[revision_count++] = after_rev;
1540 
1541   /***********************************************************************/
1542   /* GIVEN:  A and B, with common ancestor ANCESTOR, where A and B
1543      directories, and E, an entry in either A, B, or ANCESTOR.
1544 
1545      For every E, the following cases exist:
1546       - E exists in neither ANCESTOR nor A.
1547       - E doesn't exist in ANCESTOR, and has been added to A.
1548       - E exists in ANCESTOR, but has been deleted from A.
1549       - E exists in both ANCESTOR and A ...
1550         - but refers to different node revisions.
1551         - and refers to the same node revision.
1552 
1553      The same set of possible relationships with ANCESTOR holds for B,
1554      so there are thirty-six combinations.  The matrix is symmetrical
1555      with A and B reversed, so we only have to describe one triangular
1556      half, including the diagonal --- 21 combinations.
1557 
1558      Our goal here is to test all the possible scenarios that can
1559      occur given the above boolean logic table, and to make sure that
1560      the results we get are as expected.
1561 
1562      The test cases below have the following features:
1563 
1564      - They run straight through the scenarios as described in the
1565        `structure' document at this time.
1566 
1567      - In each case, a txn is begun based on some revision (ANCESTOR),
1568        is modified into a new tree (B), and then is attempted to be
1569        committed (which happens against the head of the tree, A).
1570 
1571      - If the commit is successful (and is *expected* to be such),
1572        that new revision (which exists now as a result of the
1573        successful commit) is thoroughly tested for accuracy of tree
1574        entries, and in the case of files, for their contents.  It is
1575        important to realize that these successful commits are
1576        advancing the head of the tree, and each one effective becomes
1577        the new `A' described in further test cases.
1578   */
1579   /***********************************************************************/
1580 
1581   /* (6) E exists in neither ANCESTOR nor A. */
1582   {
1583     /* (1) E exists in neither ANCESTOR nor B.  Can't occur, by
1584        assumption that E exists in either A, B, or ancestor. */
1585 
1586     /* (1) E has been added to B.  Add E in the merged result. */
1587     SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[0], pool));
1588     SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1589     SVN_ERR(svn_fs_make_file(txn_root, "theta", pool));
1590     SVN_ERR(svn_test__set_file_contents
1591             (txn_root, "theta", "This is the file 'theta'.\n", pool));
1592     SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
1593 
1594     /*********************************************************************/
1595     /* REVISION 5 */
1596     /*********************************************************************/
1597     {
1598       static svn_test__tree_entry_t expected_entries[] = {
1599         /* path, contents (0 = dir) */
1600         { "theta",         "This is the file 'theta'.\n" },
1601         { "A",             0 },
1602         { "A/mu",          "This is the file 'mu'.\n" },
1603         { "A/B",           0 },
1604         { "A/B/lambda",    "This is the file 'lambda'.\n" },
1605         { "A/B/E",         0 },
1606         { "A/B/E/alpha",   "This is the file 'alpha'.\n" },
1607         { "A/B/E/beta",    "This is the file 'beta'.\n" },
1608         { "A/B/F",         0 },
1609         { "A/C",           0 },
1610         { "A/C/kappa",     "This is the file 'kappa'.\n" },
1611         { "A/D",           0 },
1612         { "A/D/gamma",     "This is the file 'gamma'.\n" },
1613         { "A/D/G",         0 },
1614         { "A/D/G/pi",      "This is the file 'pi'.\n" },
1615         { "A/D/G/rho",     "This is the file 'rho'.\n" },
1616         { "A/D/G/tau",     "This is the file 'tau'.\n" },
1617         { "A/D/I",         0 },
1618         { "A/D/I/delta",   "This is the file 'delta'.\n" },
1619         { "A/D/I/epsilon", "This is the file 'epsilon'.\n" }
1620       };
1621       SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool));
1622       SVN_ERR(svn_test__validate_tree(revision_root,
1623                                       expected_entries,
1624                                       20, pool));
1625     }
1626     revisions[revision_count++] = after_rev;
1627 
1628     /* (1) E has been deleted from B.  Can't occur, by assumption that
1629        E doesn't exist in ANCESTOR. */
1630 
1631     /* (3) E exists in both ANCESTOR and B.  Can't occur, by
1632        assumption that E doesn't exist in ancestor. */
1633   }
1634 
1635   /* (5) E doesn't exist in ANCESTOR, and has been added to A. */
1636   {
1637     svn_revnum_t failed_rev;
1638     /* (1) E doesn't exist in ANCESTOR, and has been added to B.
1639        Conflict. */
1640     SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[4], pool));
1641     SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1642     SVN_ERR(svn_fs_make_file(txn_root, "theta", pool));
1643     SVN_ERR(svn_test__set_file_contents
1644             (txn_root, "theta", "This is another file 'theta'.\n", pool));
1645 
1646     /* TXN must actually be based upon revisions[4] (instead of HEAD). */
1647     SVN_TEST_ASSERT(svn_fs_txn_base_revision(txn) == revisions[4]);
1648 
1649     SVN_ERR(test_commit_txn(&failed_rev, txn, "/theta", pool));
1650     SVN_ERR(svn_fs_abort_txn(txn, pool));
1651 
1652     /* (1) E exists in ANCESTOR, but has been deleted from B.  Can't
1653        occur, by assumption that E doesn't exist in ANCESTOR. */
1654 
1655     /* (3) E exists in both ANCESTOR and B.  Can't occur, by assumption
1656        that E doesn't exist in ANCESTOR. */
1657 
1658     SVN_TEST_ASSERT(failed_rev == SVN_INVALID_REVNUM);
1659   }
1660 
1661   /* (4) E exists in ANCESTOR, but has been deleted from A */
1662   {
1663     /* (1) E exists in ANCESTOR, but has been deleted from B.  If
1664        neither delete was a result of a rename, then omit E from the
1665        merged tree.  Otherwise, conflict. */
1666     /* ### cmpilato todo: the rename case isn't actually handled by
1667        merge yet, so we know we won't get a conflict here. */
1668     SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[1], pool));
1669     SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1670     SVN_ERR(svn_fs_delete(txn_root, "A/D/H", pool));
1671 
1672     /* TXN must actually be based upon revisions[1] (instead of HEAD). */
1673     SVN_TEST_ASSERT(svn_fs_txn_base_revision(txn) == revisions[1]);
1674 
1675     /* We used to create the revision like this before fixing issue
1676        #2751 -- Directory prop mods reverted in overlapping commits scenario.
1677 
1678        But we now expect that to fail as out of date */
1679     {
1680       svn_revnum_t failed_rev;
1681       SVN_ERR(test_commit_txn(&failed_rev, txn, "/A/D/H", pool));
1682 
1683       SVN_TEST_ASSERT(failed_rev == SVN_INVALID_REVNUM);
1684     }
1685     /*********************************************************************/
1686     /* REVISION 6 */
1687     /*********************************************************************/
1688     {
1689       static svn_test__tree_entry_t expected_entries[] = {
1690         /* path, contents (0 = dir) */
1691         { "theta",         "This is the file 'theta'.\n" },
1692         { "A",             0 },
1693         { "A/mu",          "This is the file 'mu'.\n" },
1694         { "A/B",           0 },
1695         { "A/B/lambda",    "This is the file 'lambda'.\n" },
1696         { "A/B/E",         0 },
1697         { "A/B/E/alpha",   "This is the file 'alpha'.\n" },
1698         { "A/B/E/beta",    "This is the file 'beta'.\n" },
1699         { "A/B/F",         0 },
1700         { "A/C",           0 },
1701         { "A/C/kappa",     "This is the file 'kappa'.\n" },
1702         { "A/D",           0 },
1703         { "A/D/gamma",     "This is the file 'gamma'.\n" },
1704         { "A/D/G",         0 },
1705         { "A/D/G/pi",      "This is the file 'pi'.\n" },
1706         { "A/D/G/rho",     "This is the file 'rho'.\n" },
1707         { "A/D/G/tau",     "This is the file 'tau'.\n" },
1708         { "A/D/I",         0 },
1709         { "A/D/I/delta",   "This is the file 'delta'.\n" },
1710         { "A/D/I/epsilon", "This is the file 'epsilon'.\n" }
1711       };
1712       SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool));
1713       SVN_ERR(svn_test__validate_tree(revision_root,
1714                                       expected_entries,
1715                                       20, pool));
1716     }
1717     revisions[revision_count++] = after_rev;
1718 
1719     /* Try deleting a file F inside a subtree S where S does not exist
1720        in the most recent revision, but does exist in the ancestor
1721        tree.  This should conflict. */
1722     {
1723       svn_revnum_t failed_rev;
1724       SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[1], pool));
1725       SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1726       SVN_ERR(svn_fs_delete(txn_root, "A/D/H/omega", pool));
1727       SVN_ERR(test_commit_txn(&failed_rev, txn, "/A/D/H", pool));
1728       SVN_ERR(svn_fs_abort_txn(txn, pool));
1729 
1730       SVN_TEST_ASSERT(failed_rev == SVN_INVALID_REVNUM);
1731     }
1732 
1733     /* E exists in both ANCESTOR and B ... */
1734     {
1735       /* (1) but refers to different nodes.  Conflict. */
1736       SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, pool));
1737       SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1738       SVN_ERR(svn_fs_make_dir(txn_root, "A/D/H", pool));
1739       SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
1740       revisions[revision_count++] = after_rev;
1741 
1742       /*********************************************************************/
1743       /* REVISION 7 */
1744       /*********************************************************************/
1745 
1746       /* Re-remove A/D/H because future tests expect it to be absent. */
1747       {
1748         SVN_ERR(svn_fs_begin_txn
1749                 (&txn, fs, revisions[revision_count - 1], pool));
1750         SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1751         SVN_ERR(svn_fs_delete(txn_root, "A/D/H", pool));
1752         SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
1753         revisions[revision_count++] = after_rev;
1754       }
1755 
1756       /*********************************************************************/
1757       /* REVISION 8 (looks exactly like revision 6, we hope) */
1758       /*********************************************************************/
1759 
1760       /* (1) but refers to different revisions of the same node.
1761          Conflict. */
1762       SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[1], pool));
1763       SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1764       SVN_ERR(svn_fs_make_file(txn_root, "A/D/H/zeta", pool));
1765       SVN_ERR(test_commit_txn(&after_rev, txn, "/A/D/H", pool));
1766       SVN_ERR(svn_fs_abort_txn(txn, pool));
1767 
1768       /* (1) and refers to the same node revision.  Omit E from the
1769          merged tree.  This is already tested in Merge-Test 3
1770          (A/D/H/chi, A/D/H/psi, e.g.), but we'll test it here again
1771          anyway.  A little paranoia never hurt anyone.  */
1772       SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[1], pool));
1773       SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1774       SVN_ERR(svn_fs_delete(txn_root, "A/mu", pool)); /* unrelated change */
1775       SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
1776 
1777       /*********************************************************************/
1778       /* REVISION 9 */
1779       /*********************************************************************/
1780       {
1781         static svn_test__tree_entry_t expected_entries[] = {
1782           /* path, contents (0 = dir) */
1783           { "theta",         "This is the file 'theta'.\n" },
1784           { "A",             0 },
1785           { "A/B",           0 },
1786           { "A/B/lambda",    "This is the file 'lambda'.\n" },
1787           { "A/B/E",         0 },
1788           { "A/B/E/alpha",   "This is the file 'alpha'.\n" },
1789           { "A/B/E/beta",    "This is the file 'beta'.\n" },
1790           { "A/B/F",         0 },
1791           { "A/C",           0 },
1792           { "A/C/kappa",     "This is the file 'kappa'.\n" },
1793           { "A/D",           0 },
1794           { "A/D/gamma",     "This is the file 'gamma'.\n" },
1795           { "A/D/G",         0 },
1796           { "A/D/G/pi",      "This is the file 'pi'.\n" },
1797           { "A/D/G/rho",     "This is the file 'rho'.\n" },
1798           { "A/D/G/tau",     "This is the file 'tau'.\n" },
1799           { "A/D/I",         0 },
1800           { "A/D/I/delta",   "This is the file 'delta'.\n" },
1801           { "A/D/I/epsilon", "This is the file 'epsilon'.\n" }
1802         };
1803         SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool));
1804         SVN_ERR(svn_test__validate_tree(revision_root,
1805                                         expected_entries,
1806                                         19, pool));
1807       }
1808       revisions[revision_count++] = after_rev;
1809     }
1810   }
1811 
1812   /* Preparation for upcoming tests.
1813      We make a new head revision, with A/mu restored, but containing
1814      slightly different contents than its first incarnation. */
1815   SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[revision_count-1], pool));
1816   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1817   SVN_ERR(svn_fs_make_file(txn_root, "A/mu", pool));
1818   SVN_ERR(svn_test__set_file_contents
1819           (txn_root, "A/mu", "A new file 'mu'.\n", pool));
1820   SVN_ERR(svn_fs_make_file(txn_root, "A/D/G/xi", pool));
1821   SVN_ERR(svn_test__set_file_contents
1822           (txn_root, "A/D/G/xi", "This is the file 'xi'.\n", pool));
1823   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
1824   /*********************************************************************/
1825   /* REVISION 10 */
1826   /*********************************************************************/
1827   {
1828     static svn_test__tree_entry_t expected_entries[] = {
1829       /* path, contents (0 = dir) */
1830       { "theta",         "This is the file 'theta'.\n" },
1831       { "A",             0 },
1832       { "A/mu",          "A new file 'mu'.\n" },
1833       { "A/B",           0 },
1834       { "A/B/lambda",    "This is the file 'lambda'.\n" },
1835       { "A/B/E",         0 },
1836       { "A/B/E/alpha",   "This is the file 'alpha'.\n" },
1837       { "A/B/E/beta",    "This is the file 'beta'.\n" },
1838       { "A/B/F",         0 },
1839       { "A/C",           0 },
1840       { "A/C/kappa",     "This is the file 'kappa'.\n" },
1841       { "A/D",           0 },
1842       { "A/D/gamma",     "This is the file 'gamma'.\n" },
1843       { "A/D/G",         0 },
1844       { "A/D/G/pi",      "This is the file 'pi'.\n" },
1845       { "A/D/G/rho",     "This is the file 'rho'.\n" },
1846       { "A/D/G/tau",     "This is the file 'tau'.\n" },
1847       { "A/D/G/xi",      "This is the file 'xi'.\n" },
1848       { "A/D/I",         0 },
1849       { "A/D/I/delta",   "This is the file 'delta'.\n" },
1850       { "A/D/I/epsilon", "This is the file 'epsilon'.\n" }
1851     };
1852     SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool));
1853     SVN_ERR(svn_test__validate_tree(revision_root, expected_entries,
1854                                     21, pool));
1855   }
1856   revisions[revision_count++] = after_rev;
1857 
1858   /* (3) E exists in both ANCESTOR and A, but refers to different
1859      nodes. */
1860   {
1861     /* (1) E exists in both ANCESTOR and B, but refers to different
1862        nodes, and not all nodes are directories.  Conflict. */
1863 
1864     /* ### kff todo: A/mu's contents will be exactly the same.
1865        If the fs ever starts optimizing this case, these tests may
1866        start to fail. */
1867     SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[1], pool));
1868     SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1869     SVN_ERR(svn_fs_delete(txn_root, "A/mu", pool));
1870     SVN_ERR(svn_fs_make_file(txn_root, "A/mu", pool));
1871     SVN_ERR(svn_test__set_file_contents
1872             (txn_root, "A/mu", "This is the file 'mu'.\n", pool));
1873     SVN_ERR(test_commit_txn(&after_rev, txn, "/A/mu", pool));
1874     SVN_ERR(svn_fs_abort_txn(txn, pool));
1875 
1876     /* (1) E exists in both ANCESTOR and B, but refers to different
1877        revisions of the same node.  Conflict. */
1878     SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[1], pool));
1879     SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1880     SVN_ERR(svn_test__set_file_contents
1881             (txn_root, "A/mu", "A change to file 'mu'.\n", pool));
1882     SVN_ERR(test_commit_txn(&after_rev, txn, "/A/mu", pool));
1883     SVN_ERR(svn_fs_abort_txn(txn, pool));
1884 
1885     /* (1) E exists in both ANCESTOR and B, and refers to the same
1886        node revision.  Replace E with A's node revision.  */
1887     {
1888       svn_stringbuf_t *old_mu_contents;
1889       SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[1], pool));
1890       SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1891       SVN_ERR(svn_test__get_file_contents
1892               (txn_root, "A/mu", &old_mu_contents, pool));
1893       if ((! old_mu_contents) || (strcmp(old_mu_contents->data,
1894                                          "This is the file 'mu'.\n") != 0))
1895         {
1896           return svn_error_create
1897             (SVN_ERR_FS_GENERAL, NULL,
1898              "got wrong contents from an old revision tree");
1899         }
1900       SVN_ERR(svn_fs_make_file(txn_root, "A/sigma", pool));
1901       SVN_ERR(svn_test__set_file_contents  /* unrelated change */
1902               (txn_root, "A/sigma", "This is the file 'sigma'.\n", pool));
1903       SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
1904       /*********************************************************************/
1905       /* REVISION 11 */
1906       /*********************************************************************/
1907       {
1908         static svn_test__tree_entry_t expected_entries[] = {
1909           /* path, contents (0 = dir) */
1910           { "theta",         "This is the file 'theta'.\n" },
1911           { "A",             0 },
1912           { "A/mu",          "A new file 'mu'.\n" },
1913           { "A/sigma",       "This is the file 'sigma'.\n" },
1914           { "A/B",           0 },
1915           { "A/B/lambda",    "This is the file 'lambda'.\n" },
1916           { "A/B/E",         0 },
1917           { "A/B/E/alpha",   "This is the file 'alpha'.\n" },
1918           { "A/B/E/beta",    "This is the file 'beta'.\n" },
1919           { "A/B/F",         0 },
1920           { "A/C",           0 },
1921           { "A/C/kappa",     "This is the file 'kappa'.\n" },
1922           { "A/D",           0 },
1923           { "A/D/gamma",     "This is the file 'gamma'.\n" },
1924           { "A/D/G",         0 },
1925           { "A/D/G/pi",      "This is the file 'pi'.\n" },
1926           { "A/D/G/rho",     "This is the file 'rho'.\n" },
1927           { "A/D/G/tau",     "This is the file 'tau'.\n" },
1928           { "A/D/G/xi",      "This is the file 'xi'.\n" },
1929           { "A/D/I",         0 },
1930           { "A/D/I/delta",   "This is the file 'delta'.\n" },
1931           { "A/D/I/epsilon", "This is the file 'epsilon'.\n" }
1932         };
1933         SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool));
1934         SVN_ERR(svn_test__validate_tree(revision_root,
1935                                         expected_entries,
1936                                         22, pool));
1937       }
1938       revisions[revision_count++] = after_rev;
1939     }
1940   }
1941 
1942   /* Preparation for upcoming tests.
1943      We make a new head revision.  There are two changes in the new
1944      revision: A/B/lambda has been modified.  We will also use the
1945      recent addition of A/D/G/xi, treated as a modification to
1946      A/D/G. */
1947   SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[revision_count-1], pool));
1948   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1949   SVN_ERR(svn_test__set_file_contents
1950           (txn_root, "A/B/lambda", "Change to file 'lambda'.\n", pool));
1951   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
1952   /*********************************************************************/
1953   /* REVISION 12 */
1954   /*********************************************************************/
1955   {
1956     static svn_test__tree_entry_t expected_entries[] = {
1957       /* path, contents (0 = dir) */
1958       { "theta",         "This is the file 'theta'.\n" },
1959       { "A",             0 },
1960       { "A/mu",          "A new file 'mu'.\n" },
1961       { "A/sigma",       "This is the file 'sigma'.\n" },
1962       { "A/B",           0 },
1963       { "A/B/lambda",    "Change to file 'lambda'.\n" },
1964       { "A/B/E",         0 },
1965       { "A/B/E/alpha",   "This is the file 'alpha'.\n" },
1966       { "A/B/E/beta",    "This is the file 'beta'.\n" },
1967       { "A/B/F",         0 },
1968       { "A/C",           0 },
1969       { "A/C/kappa",     "This is the file 'kappa'.\n" },
1970       { "A/D",           0 },
1971       { "A/D/gamma",     "This is the file 'gamma'.\n" },
1972       { "A/D/G",         0 },
1973       { "A/D/G/pi",      "This is the file 'pi'.\n" },
1974       { "A/D/G/rho",     "This is the file 'rho'.\n" },
1975       { "A/D/G/tau",     "This is the file 'tau'.\n" },
1976       { "A/D/G/xi",      "This is the file 'xi'.\n" },
1977       { "A/D/I",         0 },
1978       { "A/D/I/delta",   "This is the file 'delta'.\n" },
1979       { "A/D/I/epsilon", "This is the file 'epsilon'.\n" }
1980     };
1981     SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool));
1982     SVN_ERR(svn_test__validate_tree(revision_root, expected_entries,
1983                                     22, pool));
1984   }
1985   revisions[revision_count++] = after_rev;
1986 
1987   /* (2) E exists in both ANCESTOR and A, but refers to different
1988      revisions of the same node. */
1989   {
1990     /* (1a) E exists in both ANCESTOR and B, but refers to different
1991        revisions of the same file node.  Conflict. */
1992     SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[1], pool));
1993     SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1994     SVN_ERR(svn_test__set_file_contents
1995             (txn_root, "A/B/lambda", "A different change to 'lambda'.\n",
1996              pool));
1997     SVN_ERR(test_commit_txn(&after_rev, txn, "/A/B/lambda", pool));
1998     SVN_ERR(svn_fs_abort_txn(txn, pool));
1999 
2000     /* (1b) E exists in both ANCESTOR and B, but refers to different
2001        revisions of the same directory node.  Merge A/E and B/E,
2002        recursively.  Succeed, because no conflict beneath E. */
2003     SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[1], pool));
2004     SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2005     SVN_ERR(svn_fs_make_file(txn_root, "A/D/G/nu", pool));
2006     SVN_ERR(svn_test__set_file_contents
2007             (txn_root, "A/D/G/nu", "This is the file 'nu'.\n", pool));
2008     SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
2009     /*********************************************************************/
2010     /* REVISION 13 */
2011     /*********************************************************************/
2012     {
2013       static svn_test__tree_entry_t expected_entries[] = {
2014         /* path, contents (0 = dir) */
2015         { "theta",         "This is the file 'theta'.\n" },
2016         { "A",             0 },
2017         { "A/mu",          "A new file 'mu'.\n" },
2018         { "A/sigma",       "This is the file 'sigma'.\n" },
2019         { "A/B",           0 },
2020         { "A/B/lambda",    "Change to file 'lambda'.\n" },
2021         { "A/B/E",         0 },
2022         { "A/B/E/alpha",   "This is the file 'alpha'.\n" },
2023         { "A/B/E/beta",    "This is the file 'beta'.\n" },
2024         { "A/B/F",         0 },
2025         { "A/C",           0 },
2026         { "A/C/kappa",     "This is the file 'kappa'.\n" },
2027         { "A/D",           0 },
2028         { "A/D/gamma",     "This is the file 'gamma'.\n" },
2029         { "A/D/G",         0 },
2030         { "A/D/G/pi",      "This is the file 'pi'.\n" },
2031         { "A/D/G/rho",     "This is the file 'rho'.\n" },
2032         { "A/D/G/tau",     "This is the file 'tau'.\n" },
2033         { "A/D/G/xi",      "This is the file 'xi'.\n" },
2034         { "A/D/G/nu",      "This is the file 'nu'.\n" },
2035         { "A/D/I",         0 },
2036         { "A/D/I/delta",   "This is the file 'delta'.\n" },
2037         { "A/D/I/epsilon", "This is the file 'epsilon'.\n" }
2038       };
2039       SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool));
2040       SVN_ERR(svn_test__validate_tree(revision_root,
2041                                       expected_entries,
2042                                       23, pool));
2043     }
2044     revisions[revision_count++] = after_rev;
2045 
2046     /* (1c) E exists in both ANCESTOR and B, but refers to different
2047        revisions of the same directory node.  Merge A/E and B/E,
2048        recursively.  Fail, because conflict beneath E. */
2049     SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[1], pool));
2050     SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2051     SVN_ERR(svn_fs_make_file(txn_root, "A/D/G/xi", pool));
2052     SVN_ERR(svn_test__set_file_contents
2053             (txn_root, "A/D/G/xi", "This is a different file 'xi'.\n", pool));
2054     SVN_ERR(test_commit_txn(&after_rev, txn, "/A/D/G/xi", pool));
2055     SVN_ERR(svn_fs_abort_txn(txn, pool));
2056 
2057     /* (1) E exists in both ANCESTOR and B, and refers to the same node
2058        revision.  Replace E with A's node revision.  */
2059     {
2060       svn_stringbuf_t *old_lambda_ctnts;
2061       SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[1], pool));
2062       SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2063       SVN_ERR(svn_test__get_file_contents
2064               (txn_root, "A/B/lambda", &old_lambda_ctnts, pool));
2065       if ((! old_lambda_ctnts)
2066           || (strcmp(old_lambda_ctnts->data,
2067                      "This is the file 'lambda'.\n") != 0))
2068         {
2069           return svn_error_create
2070             (SVN_ERR_FS_GENERAL, NULL,
2071              "got wrong contents from an old revision tree");
2072         }
2073       SVN_ERR(svn_test__set_file_contents
2074               (txn_root, "A/D/G/rho",
2075                "This is an irrelevant change to 'rho'.\n", pool));
2076       SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
2077       /*********************************************************************/
2078       /* REVISION 14 */
2079       /*********************************************************************/
2080       {
2081         static svn_test__tree_entry_t expected_entries[] = {
2082           /* path, contents (0 = dir) */
2083           { "theta",         "This is the file 'theta'.\n" },
2084           { "A",             0 },
2085           { "A/mu",          "A new file 'mu'.\n" },
2086           { "A/sigma",       "This is the file 'sigma'.\n" },
2087           { "A/B",           0 },
2088           { "A/B/lambda",    "Change to file 'lambda'.\n" },
2089           { "A/B/E",         0 },
2090           { "A/B/E/alpha",   "This is the file 'alpha'.\n" },
2091           { "A/B/E/beta",    "This is the file 'beta'.\n" },
2092           { "A/B/F",         0 },
2093           { "A/C",           0 },
2094           { "A/C/kappa",     "This is the file 'kappa'.\n" },
2095           { "A/D",           0 },
2096           { "A/D/gamma",     "This is the file 'gamma'.\n" },
2097           { "A/D/G",         0 },
2098           { "A/D/G/pi",      "This is the file 'pi'.\n" },
2099           { "A/D/G/rho",     "This is an irrelevant change to 'rho'.\n" },
2100           { "A/D/G/tau",     "This is the file 'tau'.\n" },
2101           { "A/D/G/xi",      "This is the file 'xi'.\n" },
2102           { "A/D/G/nu",      "This is the file 'nu'.\n"},
2103           { "A/D/I",         0 },
2104           { "A/D/I/delta",   "This is the file 'delta'.\n" },
2105           { "A/D/I/epsilon", "This is the file 'epsilon'.\n" }
2106         };
2107         SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool));
2108         SVN_ERR(svn_test__validate_tree(revision_root,
2109                                         expected_entries,
2110                                         23, pool));
2111       }
2112       revisions[revision_count++] = after_rev;
2113     }
2114   }
2115 
2116   /* (1) E exists in both ANCESTOR and A, and refers to the same node
2117      revision. */
2118   {
2119     /* (1) E exists in both ANCESTOR and B, and refers to the same
2120        node revision.  Nothing has happened to ANCESTOR/E, so no
2121        change is necessary. */
2122 
2123     /* This has now been tested about fifty-four trillion times.  We
2124        don't need to test it again here. */
2125   }
2126 
2127   /* E exists in ANCESTOR, but has been deleted from A.  E exists in
2128      both ANCESTOR and B but refers to different revisions of the same
2129      node.  Conflict.  */
2130   SVN_ERR(svn_fs_begin_txn(&txn, fs, revisions[1], pool));
2131   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2132   SVN_ERR(svn_test__set_file_contents
2133           (txn_root, "iota", "New contents for 'iota'.\n", pool));
2134   SVN_ERR(test_commit_txn(&after_rev, txn, "/iota", pool));
2135   SVN_ERR(svn_fs_abort_txn(txn, pool));
2136 
2137   return SVN_NO_ERROR;
2138 }
2139 
2140 
2141 static svn_error_t *
copy_test(const svn_test_opts_t * opts,apr_pool_t * pool)2142 copy_test(const svn_test_opts_t *opts,
2143           apr_pool_t *pool)
2144 {
2145   svn_fs_t *fs;
2146   svn_fs_txn_t *txn;
2147   svn_fs_root_t *txn_root, *rev_root;
2148   svn_revnum_t after_rev;
2149 
2150   /* Prepare a filesystem. */
2151   SVN_ERR(svn_test__create_fs(&fs, "test-repo-copy",
2152                               opts, pool));
2153 
2154   /* In first txn, create and commit the greek tree. */
2155   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
2156   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2157   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
2158   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
2159 
2160   /* In second txn, copy the file A/D/G/pi into the subtree A/D/H as
2161      pi2.  Change that file's contents to state its new name.  Along
2162      the way, test that the copy history was preserved both during the
2163      transaction and after the commit. */
2164 
2165   SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, pool));
2166   SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, pool));
2167   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2168   SVN_ERR(svn_fs_copy(rev_root, "A/D/G/pi",
2169                       txn_root, "A/D/H/pi2",
2170                       pool));
2171   { /* Check that copy history was preserved. */
2172     svn_revnum_t rev;
2173     const char *path;
2174 
2175     SVN_ERR(svn_fs_copied_from(&rev, &path, txn_root,
2176                                "A/D/H/pi2", pool));
2177 
2178     if (rev != after_rev)
2179       return svn_error_create
2180         (SVN_ERR_FS_GENERAL, NULL,
2181          "pre-commit copy history not preserved (rev lost) for A/D/H/pi2");
2182 
2183     if (strcmp(path, "/A/D/G/pi") != 0)
2184       return svn_error_create
2185         (SVN_ERR_FS_GENERAL, NULL,
2186          "pre-commit copy history not preserved (path lost) for A/D/H/pi2");
2187   }
2188   SVN_ERR(svn_test__set_file_contents
2189           (txn_root, "A/D/H/pi2", "This is the file 'pi2'.\n", pool));
2190   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
2191 
2192   { /* Check that copy history is still preserved _after_ the commit. */
2193     svn_fs_root_t *root;
2194     svn_revnum_t rev;
2195     const char *path;
2196 
2197     SVN_ERR(svn_fs_revision_root(&root, fs, after_rev, pool));
2198     SVN_ERR(svn_fs_copied_from(&rev, &path, root, "A/D/H/pi2", pool));
2199 
2200     if (rev != (after_rev - 1))
2201       return svn_error_create
2202         (SVN_ERR_FS_GENERAL, NULL,
2203          "post-commit copy history wrong (rev) for A/D/H/pi2");
2204 
2205     if (strcmp(path, "/A/D/G/pi") != 0)
2206       return svn_error_create
2207         (SVN_ERR_FS_GENERAL, NULL,
2208          "post-commit copy history wrong (path) for A/D/H/pi2");
2209   }
2210 
2211   /* Let's copy the copy we just made, to make sure copy history gets
2212      chained correctly. */
2213   SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, pool));
2214   SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, pool));
2215   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2216   SVN_ERR(svn_fs_copy(rev_root, "A/D/H/pi2", txn_root, "A/D/H/pi3", pool));
2217   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
2218   { /* Check the copy history. */
2219     svn_fs_root_t *root;
2220     svn_revnum_t rev;
2221     const char *path;
2222 
2223     /* Check that the original copy still has its old history. */
2224     SVN_ERR(svn_fs_revision_root(&root, fs, (after_rev - 1), pool));
2225     SVN_ERR(svn_fs_copied_from(&rev, &path, root, "A/D/H/pi2", pool));
2226 
2227     if (rev != (after_rev - 2))
2228       return svn_error_create
2229         (SVN_ERR_FS_GENERAL, NULL,
2230          "first copy history wrong (rev) for A/D/H/pi2");
2231 
2232     if (strcmp(path, "/A/D/G/pi") != 0)
2233       return svn_error_create
2234         (SVN_ERR_FS_GENERAL, NULL,
2235          "first copy history wrong (path) for A/D/H/pi2");
2236 
2237     /* Check that the copy of the copy has the right history. */
2238     SVN_ERR(svn_fs_revision_root(&root, fs, after_rev, pool));
2239     SVN_ERR(svn_fs_copied_from(&rev, &path, root, "A/D/H/pi3", pool));
2240 
2241     if (rev != (after_rev - 1))
2242       return svn_error_create
2243         (SVN_ERR_FS_GENERAL, NULL,
2244          "second copy history wrong (rev) for A/D/H/pi3");
2245 
2246     if (strcmp(path, "/A/D/H/pi2") != 0)
2247       return svn_error_create
2248         (SVN_ERR_FS_GENERAL, NULL,
2249          "second copy history wrong (path) for A/D/H/pi3");
2250   }
2251 
2252   /* Commit a regular change to a copy, make sure the copy history
2253      isn't inherited. */
2254   SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, pool));
2255   SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, pool));
2256   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2257   SVN_ERR(svn_test__set_file_contents
2258           (txn_root, "A/D/H/pi3", "This is the file 'pi3'.\n", pool));
2259   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
2260   { /* Check the copy history. */
2261     svn_fs_root_t *root;
2262     svn_revnum_t rev;
2263     const char *path;
2264 
2265     /* Check that the copy still has its history. */
2266     SVN_ERR(svn_fs_revision_root(&root, fs, (after_rev - 1), pool));
2267     SVN_ERR(svn_fs_copied_from(&rev, &path, root, "A/D/H/pi3", pool));
2268 
2269     if (rev != (after_rev - 2))
2270       return svn_error_create
2271         (SVN_ERR_FS_GENERAL, NULL,
2272          "copy history wrong (rev) for A/D/H/pi3");
2273 
2274     if (strcmp(path, "/A/D/H/pi2") != 0)
2275       return svn_error_create
2276         (SVN_ERR_FS_GENERAL, NULL,
2277          "copy history wrong (path) for A/D/H/pi3");
2278 
2279     /* Check that the next revision after the copy has no copy history. */
2280     SVN_ERR(svn_fs_revision_root(&root, fs, after_rev, pool));
2281     SVN_ERR(svn_fs_copied_from(&rev, &path, root, "A/D/H/pi3", pool));
2282 
2283     if (rev != SVN_INVALID_REVNUM)
2284       return svn_error_create
2285         (SVN_ERR_FS_GENERAL, NULL,
2286          "copy history wrong (rev) for A/D/H/pi3");
2287 
2288     if (path != NULL)
2289       return svn_error_create
2290         (SVN_ERR_FS_GENERAL, NULL,
2291          "copy history wrong (path) for A/D/H/pi3");
2292   }
2293 
2294   /* Then, as if that wasn't fun enough, copy the whole subtree A/D/H
2295      into the root directory as H2! */
2296   SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, pool));
2297   SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, pool));
2298   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2299   SVN_ERR(svn_fs_copy(rev_root, "A/D/H", txn_root, "H2", pool));
2300   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
2301   { /* Check the copy history. */
2302     svn_fs_root_t *root;
2303     svn_revnum_t rev;
2304     const char *path;
2305 
2306     /* Check that the top of the copy has history. */
2307     SVN_ERR(svn_fs_revision_root(&root, fs, after_rev, pool));
2308     SVN_ERR(svn_fs_copied_from(&rev, &path, root, "H2", pool));
2309 
2310     if (rev != (after_rev - 1))
2311       return svn_error_create
2312         (SVN_ERR_FS_GENERAL, NULL,
2313          "copy history wrong (rev) for H2");
2314 
2315     if (strcmp(path, "/A/D/H") != 0)
2316       return svn_error_create
2317         (SVN_ERR_FS_GENERAL, NULL,
2318          "copy history wrong (path) for H2");
2319 
2320     /* Check that a random file under H2 reports no copy history. */
2321     SVN_ERR(svn_fs_copied_from(&rev, &path, root, "H2/omega", pool));
2322 
2323     if (rev != SVN_INVALID_REVNUM)
2324       return svn_error_create
2325         (SVN_ERR_FS_GENERAL, NULL,
2326          "copy history wrong (rev) for H2/omega");
2327 
2328     if (path != NULL)
2329       return svn_error_create
2330         (SVN_ERR_FS_GENERAL, NULL,
2331          "copy history wrong (path) for H2/omega");
2332 
2333     /* Note that H2/pi2 still has copy history, though.  See the doc
2334        string for svn_fs_copied_from() for more on this. */
2335   }
2336 
2337   /* Let's live dangerously.  What happens if we copy a path into one
2338      of its own children.  Looping filesystem?  Cyclic ancestry?
2339      Another West Virginia family tree with no branches?  We certainly
2340      hope that's not the case. */
2341   SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, pool));
2342   SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, pool));
2343   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2344   SVN_ERR(svn_fs_copy(rev_root, "A/B", txn_root, "A/B/E/B", pool));
2345   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
2346   { /* Check the copy history. */
2347     svn_fs_root_t *root;
2348     svn_revnum_t rev;
2349     const char *path;
2350 
2351     /* Check that the copy has history. */
2352     SVN_ERR(svn_fs_revision_root(&root, fs, after_rev, pool));
2353     SVN_ERR(svn_fs_copied_from(&rev, &path, root, "A/B/E/B", pool));
2354 
2355     if (rev != (after_rev - 1))
2356       return svn_error_create
2357         (SVN_ERR_FS_GENERAL, NULL,
2358          "copy history wrong (rev) for A/B/E/B");
2359 
2360     if (strcmp(path, "/A/B") != 0)
2361       return svn_error_create
2362         (SVN_ERR_FS_GENERAL, NULL,
2363          "copy history wrong (path) for A/B/E/B");
2364 
2365     /* Check that the original does not have copy history. */
2366     SVN_ERR(svn_fs_revision_root(&root, fs, after_rev, pool));
2367     SVN_ERR(svn_fs_copied_from(&rev, &path, root, "A/B", pool));
2368 
2369     if (rev != SVN_INVALID_REVNUM)
2370       return svn_error_create
2371         (SVN_ERR_FS_GENERAL, NULL,
2372          "copy history wrong (rev) for A/B");
2373 
2374     if (path != NULL)
2375       return svn_error_create
2376         (SVN_ERR_FS_GENERAL, NULL,
2377          "copy history wrong (path) for A/B");
2378   }
2379 
2380   /* After all these changes, let's see if the filesystem looks as we
2381      would expect it to. */
2382   {
2383     static svn_test__tree_entry_t expected_entries[] = {
2384       /* path, contents (0 = dir) */
2385       { "iota",        "This is the file 'iota'.\n" },
2386       { "H2",          0 },
2387       { "H2/chi",      "This is the file 'chi'.\n" },
2388       { "H2/pi2",      "This is the file 'pi2'.\n" },
2389       { "H2/pi3",      "This is the file 'pi3'.\n" },
2390       { "H2/psi",      "This is the file 'psi'.\n" },
2391       { "H2/omega",    "This is the file 'omega'.\n" },
2392       { "A",           0 },
2393       { "A/mu",        "This is the file 'mu'.\n" },
2394       { "A/B",         0 },
2395       { "A/B/lambda",  "This is the file 'lambda'.\n" },
2396       { "A/B/E",       0 },
2397       { "A/B/E/alpha", "This is the file 'alpha'.\n" },
2398       { "A/B/E/beta",  "This is the file 'beta'.\n" },
2399       { "A/B/E/B",         0 },
2400       { "A/B/E/B/lambda",  "This is the file 'lambda'.\n" },
2401       { "A/B/E/B/E",       0 },
2402       { "A/B/E/B/E/alpha", "This is the file 'alpha'.\n" },
2403       { "A/B/E/B/E/beta",  "This is the file 'beta'.\n" },
2404       { "A/B/E/B/F",       0 },
2405       { "A/B/F",       0 },
2406       { "A/C",         0 },
2407       { "A/D",         0 },
2408       { "A/D/gamma",   "This is the file 'gamma'.\n" },
2409       { "A/D/G",       0 },
2410       { "A/D/G/pi",    "This is the file 'pi'.\n" },
2411       { "A/D/G/rho",   "This is the file 'rho'.\n" },
2412       { "A/D/G/tau",   "This is the file 'tau'.\n" },
2413       { "A/D/H",       0 },
2414       { "A/D/H/chi",   "This is the file 'chi'.\n" },
2415       { "A/D/H/pi2",   "This is the file 'pi2'.\n" },
2416       { "A/D/H/pi3",   "This is the file 'pi3'.\n" },
2417       { "A/D/H/psi",   "This is the file 'psi'.\n" },
2418       { "A/D/H/omega", "This is the file 'omega'.\n" }
2419     };
2420     SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, pool));
2421     SVN_ERR(svn_test__validate_tree(rev_root, expected_entries,
2422                                     34, pool));
2423   }
2424 
2425   return SVN_NO_ERROR;
2426 }
2427 
2428 
2429 /* This tests deleting of mutable nodes.  We build a tree in a
2430  * transaction, then try to delete various items in the tree.  We
2431  * never commit the tree, so every entry being deleted points to a
2432  * mutable node.
2433  *
2434  * ### todo: this test was written before commits worked.  It might
2435  * now be worthwhile to combine it with delete().
2436  */
2437 static svn_error_t *
delete_mutables(const svn_test_opts_t * opts,apr_pool_t * pool)2438 delete_mutables(const svn_test_opts_t *opts,
2439                 apr_pool_t *pool)
2440 {
2441   svn_fs_t *fs;
2442   svn_fs_txn_t *txn;
2443   svn_fs_root_t *txn_root;
2444   svn_error_t *err;
2445 
2446   /* Prepare a txn to receive the greek tree. */
2447   SVN_ERR(svn_test__create_fs(&fs, "test-repo-del-from-dir",
2448                               opts, pool));
2449   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
2450   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2451 
2452   /* Create the greek tree. */
2453   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
2454 
2455   /* Baby, it's time to test like you've never tested before.  We do
2456    * the following, in this order:
2457    *
2458    *    1. Delete a single file somewhere, succeed.
2459    *    2. Delete two files of three, then make sure the third remains.
2460    *    3. Delete the third and last file.
2461    *    4. Try again to delete the dir, succeed.
2462    *    5. Delete one of the natively empty dirs, succeed.
2463    *    6. Try to delete root, fail.
2464    *    7. Try to delete a top-level file, succeed.
2465    *
2466    * Specifically, that's:
2467    *
2468    *    1. Delete A/D/gamma.
2469    *    2. Delete A/D/G/pi, A/D/G/rho.
2470    *    3. Delete A/D/G/tau.
2471    *    4. Try again to delete A/D/G, succeed.
2472    *    5. Delete A/C.
2473    *    6. Try to delete /, fail.
2474    *    7. Try to delete iota, succeed.
2475    *
2476    * Before and after each deletion or attempted deletion, we probe
2477    * the affected directory, to make sure everything is as it should
2478    * be.
2479    */
2480 
2481   /* 1 */
2482   {
2483     const svn_fs_id_t *gamma_id;
2484     SVN_ERR(svn_fs_node_id(&gamma_id, txn_root, "A/D/gamma", pool));
2485     SVN_ERR(check_entry_present(txn_root, "A/D", "gamma", pool));
2486     SVN_ERR(svn_fs_delete(txn_root, "A/D/gamma", pool));
2487     SVN_ERR(check_entry_absent(txn_root, "A/D", "gamma", pool));
2488   }
2489 
2490   /* 2 */
2491   {
2492     const svn_fs_id_t *pi_id, *rho_id, *tau_id;
2493     SVN_ERR(svn_fs_node_id(&pi_id, txn_root, "A/D/G/pi", pool));
2494     SVN_ERR(svn_fs_node_id(&rho_id, txn_root, "A/D/G/rho", pool));
2495     SVN_ERR(svn_fs_node_id(&tau_id, txn_root, "A/D/G/tau", pool));
2496     SVN_ERR(check_entry_present(txn_root, "A/D/G", "pi", pool));
2497     SVN_ERR(check_entry_present(txn_root, "A/D/G", "rho", pool));
2498     SVN_ERR(check_entry_present(txn_root, "A/D/G", "tau", pool));
2499     SVN_ERR(svn_fs_delete(txn_root, "A/D/G/pi", pool));
2500     SVN_ERR(check_entry_absent(txn_root, "A/D/G", "pi", pool));
2501     SVN_ERR(check_entry_present(txn_root, "A/D/G", "rho", pool));
2502     SVN_ERR(check_entry_present(txn_root, "A/D/G", "tau", pool));
2503     SVN_ERR(svn_fs_delete(txn_root, "A/D/G/rho", pool));
2504     SVN_ERR(check_entry_absent(txn_root, "A/D/G", "pi", pool));
2505     SVN_ERR(check_entry_absent(txn_root, "A/D/G", "rho", pool));
2506     SVN_ERR(check_entry_present(txn_root, "A/D/G", "tau", pool));
2507   }
2508 
2509   /* 3 */
2510   {
2511     const svn_fs_id_t *tau_id;
2512     SVN_ERR(svn_fs_node_id(&tau_id, txn_root, "A/D/G/tau", pool));
2513     SVN_ERR(check_entry_present(txn_root, "A/D/G", "tau", pool));
2514     SVN_ERR(svn_fs_delete(txn_root, "A/D/G/tau", pool));
2515     SVN_ERR(check_entry_absent(txn_root, "A/D/G", "tau", pool));
2516   }
2517 
2518   /* 4 */
2519   {
2520     const svn_fs_id_t *G_id;
2521     SVN_ERR(svn_fs_node_id(&G_id, txn_root, "A/D/G", pool));
2522     SVN_ERR(check_entry_present(txn_root, "A/D", "G", pool));
2523     SVN_ERR(svn_fs_delete(txn_root, "A/D/G", pool));        /* succeed */
2524     SVN_ERR(check_entry_absent(txn_root, "A/D", "G", pool));
2525   }
2526 
2527   /* 5 */
2528   {
2529     const svn_fs_id_t *C_id;
2530     SVN_ERR(svn_fs_node_id(&C_id, txn_root, "A/C", pool));
2531     SVN_ERR(check_entry_present(txn_root, "A", "C", pool));
2532     SVN_ERR(svn_fs_delete(txn_root, "A/C", pool));
2533     SVN_ERR(check_entry_absent(txn_root, "A", "C", pool));
2534   }
2535 
2536   /* 6 */
2537   {
2538     const svn_fs_id_t *root_id;
2539     SVN_ERR(svn_fs_node_id(&root_id, txn_root, "", pool));
2540 
2541     err = svn_fs_delete(txn_root, "", pool);
2542 
2543     if (err && (err->apr_err != SVN_ERR_FS_ROOT_DIR))
2544       {
2545         return svn_error_createf
2546           (SVN_ERR_FS_GENERAL, NULL,
2547            "deleting root directory got wrong error");
2548       }
2549     else if (! err)
2550       {
2551         return svn_error_createf
2552           (SVN_ERR_FS_GENERAL, NULL,
2553            "deleting root directory failed to get error");
2554       }
2555     svn_error_clear(err);
2556 
2557   }
2558 
2559   /* 7 */
2560   {
2561     const svn_fs_id_t *iota_id;
2562     SVN_ERR(svn_fs_node_id(&iota_id, txn_root, "iota", pool));
2563     SVN_ERR(check_entry_present(txn_root, "", "iota", pool));
2564     SVN_ERR(svn_fs_delete(txn_root, "iota", pool));
2565     SVN_ERR(check_entry_absent(txn_root, "", "iota", pool));
2566   }
2567 
2568   return SVN_NO_ERROR;
2569 }
2570 
2571 
2572 /* This tests deleting in general.
2573  *
2574  * ### todo: this test was written after (and independently of)
2575  * delete_mutables().  It might be worthwhile to combine them.
2576  */
2577 static svn_error_t *
delete(const svn_test_opts_t * opts,apr_pool_t * pool)2578 delete(const svn_test_opts_t *opts,
2579        apr_pool_t *pool)
2580 {
2581   svn_fs_t *fs;
2582   svn_fs_txn_t *txn;
2583   svn_fs_root_t *txn_root;
2584   svn_revnum_t new_rev;
2585 
2586   /* This function tests 5 cases:
2587    *
2588    * 1. Delete mutable file.
2589    * 2. Delete mutable directory.
2590    * 3. Delete mutable directory with immutable nodes.
2591    * 4. Delete immutable file.
2592    * 5. Delete immutable directory.
2593    */
2594 
2595   /* Prepare a txn to receive the greek tree. */
2596   SVN_ERR(svn_test__create_fs(&fs, "test-repo-del-tree",
2597                               opts, pool));
2598   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
2599   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2600 
2601   /* Create the greek tree. */
2602   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
2603 
2604   /* 1. Delete mutable file. */
2605   {
2606     const svn_fs_id_t *iota_id, *gamma_id;
2607     static svn_test__tree_entry_t expected_entries[] = {
2608       /* path, contents (0 = dir) */
2609       { "A",           0 },
2610       { "A/mu",        "This is the file 'mu'.\n" },
2611       { "A/B",         0 },
2612       { "A/B/lambda",  "This is the file 'lambda'.\n" },
2613       { "A/B/E",       0 },
2614       { "A/B/E/alpha", "This is the file 'alpha'.\n" },
2615       { "A/B/E/beta",  "This is the file 'beta'.\n" },
2616       { "A/C",         0 },
2617       { "A/B/F",       0 },
2618       { "A/D",         0 },
2619       { "A/D/G",       0 },
2620       { "A/D/G/pi",    "This is the file 'pi'.\n" },
2621       { "A/D/G/rho",   "This is the file 'rho'.\n" },
2622       { "A/D/G/tau",   "This is the file 'tau'.\n" },
2623       { "A/D/H",       0 },
2624       { "A/D/H/chi",   "This is the file 'chi'.\n" },
2625       { "A/D/H/psi",   "This is the file 'psi'.\n" },
2626       { "A/D/H/omega", "This is the file 'omega'.\n" }
2627     };
2628 
2629     /* Check nodes revision ID is gone.  */
2630     SVN_ERR(svn_fs_node_id(&iota_id, txn_root, "iota", pool));
2631     SVN_ERR(svn_fs_node_id(&gamma_id, txn_root, "A/D/gamma", pool));
2632 
2633     SVN_ERR(check_entry_present(txn_root, "", "iota", pool));
2634 
2635     /* Try deleting mutable files. */
2636     SVN_ERR(svn_fs_delete(txn_root, "iota", pool));
2637     SVN_ERR(svn_fs_delete(txn_root, "A/D/gamma", pool));
2638     SVN_ERR(check_entry_absent(txn_root, "", "iota", pool));
2639     SVN_ERR(check_entry_absent(txn_root, "A/D", "gamma", pool));
2640 
2641     /* Validate the tree.  */
2642     SVN_ERR(svn_test__validate_tree(txn_root, expected_entries, 18, pool));
2643   }
2644   /* Abort transaction.  */
2645   SVN_ERR(svn_fs_abort_txn(txn, pool));
2646 
2647   /* 2. Delete mutable directory. */
2648 
2649   /* Prepare a txn to receive the greek tree. */
2650   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
2651   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2652 
2653   /* Create the greek tree. */
2654   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
2655 
2656   {
2657     const svn_fs_id_t *A_id, *mu_id, *B_id, *lambda_id, *E_id, *alpha_id,
2658       *beta_id, *F_id, *C_id, *D_id, *gamma_id, *H_id, *chi_id,
2659       *psi_id, *omega_id, *G_id, *pi_id, *rho_id, *tau_id;
2660 
2661     /* Check nodes revision ID is gone.  */
2662     SVN_ERR(svn_fs_node_id(&A_id, txn_root, "/A", pool));
2663     SVN_ERR(check_entry_present(txn_root, "", "A", pool));
2664     SVN_ERR(svn_fs_node_id(&mu_id, txn_root, "/A/mu", pool));
2665     SVN_ERR(check_entry_present(txn_root, "A", "mu", pool));
2666     SVN_ERR(svn_fs_node_id(&B_id, txn_root, "/A/B", pool));
2667     SVN_ERR(check_entry_present(txn_root, "A", "B", pool));
2668     SVN_ERR(svn_fs_node_id(&lambda_id, txn_root, "/A/B/lambda", pool));
2669     SVN_ERR(check_entry_present(txn_root, "A/B", "lambda", pool));
2670     SVN_ERR(svn_fs_node_id(&E_id, txn_root, "/A/B/E", pool));
2671     SVN_ERR(check_entry_present(txn_root, "A/B", "E", pool));
2672     SVN_ERR(svn_fs_node_id(&alpha_id, txn_root, "/A/B/E/alpha", pool));
2673     SVN_ERR(check_entry_present(txn_root, "A/B/E", "alpha", pool));
2674     SVN_ERR(svn_fs_node_id(&beta_id, txn_root, "/A/B/E/beta", pool));
2675     SVN_ERR(check_entry_present(txn_root, "A/B/E", "beta", pool));
2676     SVN_ERR(svn_fs_node_id(&F_id, txn_root, "/A/B/F", pool));
2677     SVN_ERR(check_entry_present(txn_root, "A/B", "F", pool));
2678     SVN_ERR(svn_fs_node_id(&C_id, txn_root, "/A/C", pool));
2679     SVN_ERR(check_entry_present(txn_root, "A", "C", pool));
2680     SVN_ERR(svn_fs_node_id(&D_id, txn_root, "/A/D", pool));
2681     SVN_ERR(check_entry_present(txn_root, "A", "D", pool));
2682     SVN_ERR(svn_fs_node_id(&gamma_id, txn_root, "/A/D/gamma", pool));
2683     SVN_ERR(check_entry_present(txn_root, "A/D", "gamma", pool));
2684     SVN_ERR(svn_fs_node_id(&H_id, txn_root, "/A/D/H", pool));
2685     SVN_ERR(check_entry_present(txn_root, "A/D", "H", pool));
2686     SVN_ERR(svn_fs_node_id(&chi_id, txn_root, "/A/D/H/chi", pool));
2687     SVN_ERR(check_entry_present(txn_root, "A/D/H", "chi", pool));
2688     SVN_ERR(svn_fs_node_id(&psi_id, txn_root, "/A/D/H/psi", pool));
2689     SVN_ERR(check_entry_present(txn_root, "A/D/H", "psi", pool));
2690     SVN_ERR(svn_fs_node_id(&omega_id, txn_root, "/A/D/H/omega", pool));
2691     SVN_ERR(check_entry_present(txn_root, "A/D/H", "omega", pool));
2692     SVN_ERR(svn_fs_node_id(&G_id, txn_root, "/A/D/G", pool));
2693     SVN_ERR(check_entry_present(txn_root, "A/D", "G", pool));
2694     SVN_ERR(svn_fs_node_id(&pi_id, txn_root, "/A/D/G/pi", pool));
2695     SVN_ERR(check_entry_present(txn_root, "A/D/G", "pi", pool));
2696     SVN_ERR(svn_fs_node_id(&rho_id, txn_root, "/A/D/G/rho", pool));
2697     SVN_ERR(check_entry_present(txn_root, "A/D/G", "rho", pool));
2698     SVN_ERR(svn_fs_node_id(&tau_id, txn_root, "/A/D/G/tau", pool));
2699     SVN_ERR(check_entry_present(txn_root, "A/D/G", "tau", pool));
2700 
2701     /* Try deleting a mutable empty dir. */
2702     SVN_ERR(svn_fs_delete(txn_root, "A/C", pool));
2703     SVN_ERR(svn_fs_delete(txn_root, "A/B/F", pool));
2704     SVN_ERR(check_entry_absent(txn_root, "A", "C", pool));
2705     SVN_ERR(check_entry_absent(txn_root, "A/B", "F", pool));
2706 
2707     /* Now delete a mutable non-empty dir. */
2708     SVN_ERR(svn_fs_delete(txn_root, "A", pool));
2709     SVN_ERR(check_entry_absent(txn_root, "", "A", pool));
2710 
2711     /* Validate the tree.  */
2712     {
2713       static svn_test__tree_entry_t expected_entries[] = {
2714         /* path, contents (0 = dir) */
2715         { "iota",        "This is the file 'iota'.\n" } };
2716       SVN_ERR(svn_test__validate_tree(txn_root, expected_entries, 1, pool));
2717     }
2718   }
2719 
2720   /* Abort transaction.  */
2721   SVN_ERR(svn_fs_abort_txn(txn, pool));
2722 
2723   /* 3. Delete mutable directory with immutable nodes. */
2724 
2725   /* Prepare a txn to receive the greek tree. */
2726   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
2727   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2728 
2729   /* Create the greek tree. */
2730   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
2731 
2732   /* Commit the greek tree. */
2733   SVN_ERR(svn_fs_commit_txn(NULL, &new_rev, txn, pool));
2734   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(new_rev));
2735 
2736   /* Create new transaction. */
2737   SVN_ERR(svn_fs_begin_txn(&txn, fs, new_rev, pool));
2738   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2739 
2740   {
2741     const svn_fs_id_t *A_id, *mu_id, *B_id, *lambda_id, *E_id, *alpha_id,
2742       *beta_id, *F_id, *C_id, *D_id, *gamma_id, *H_id, *chi_id,
2743       *psi_id, *omega_id, *G_id, *pi_id, *rho_id, *tau_id, *sigma_id;
2744 
2745     /* Create A/D/G/sigma.  This makes all components of A/D/G
2746        mutable.  */
2747     SVN_ERR(svn_fs_make_file(txn_root, "A/D/G/sigma", pool));
2748     SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/G/sigma",
2749                                         "This is another file 'sigma'.\n", pool));
2750 
2751     /* Check that mutable node-revision-IDs are removed and immutable
2752        ones still exist.  */
2753     SVN_ERR(svn_fs_node_id(&A_id, txn_root, "/A", pool));
2754     SVN_ERR(check_entry_present(txn_root, "", "A", pool));
2755     SVN_ERR(svn_fs_node_id(&mu_id, txn_root, "/A/mu", pool));
2756     SVN_ERR(check_entry_present(txn_root, "A", "mu", pool));
2757     SVN_ERR(svn_fs_node_id(&B_id, txn_root, "/A/B", pool));
2758     SVN_ERR(check_entry_present(txn_root, "A", "B", pool));
2759     SVN_ERR(svn_fs_node_id(&lambda_id, txn_root, "/A/B/lambda", pool));
2760     SVN_ERR(check_entry_present(txn_root, "A/B", "lambda", pool));
2761     SVN_ERR(svn_fs_node_id(&E_id, txn_root, "/A/B/E", pool));
2762     SVN_ERR(check_entry_present(txn_root, "A/B", "E", pool));
2763     SVN_ERR(svn_fs_node_id(&alpha_id, txn_root, "/A/B/E/alpha", pool));
2764     SVN_ERR(check_entry_present(txn_root, "A/B/E", "alpha", pool));
2765     SVN_ERR(svn_fs_node_id(&beta_id, txn_root, "/A/B/E/beta", pool));
2766     SVN_ERR(check_entry_present(txn_root, "A/B/E", "beta", pool));
2767     SVN_ERR(svn_fs_node_id(&F_id, txn_root, "/A/B/F", pool));
2768     SVN_ERR(check_entry_present(txn_root, "A/B", "F", pool));
2769     SVN_ERR(svn_fs_node_id(&C_id, txn_root, "/A/C", pool));
2770     SVN_ERR(check_entry_present(txn_root, "A", "C", pool));
2771     SVN_ERR(svn_fs_node_id(&D_id, txn_root, "/A/D", pool));
2772     SVN_ERR(check_entry_present(txn_root, "A", "D", pool));
2773     SVN_ERR(svn_fs_node_id(&gamma_id, txn_root, "/A/D/gamma", pool));
2774     SVN_ERR(check_entry_present(txn_root, "A/D", "gamma", pool));
2775     SVN_ERR(svn_fs_node_id(&H_id, txn_root, "/A/D/H", pool));
2776     SVN_ERR(check_entry_present(txn_root, "A/D", "H", pool));
2777     SVN_ERR(svn_fs_node_id(&chi_id, txn_root, "/A/D/H/chi", pool));
2778     SVN_ERR(check_entry_present(txn_root, "A/D/H", "chi", pool));
2779     SVN_ERR(svn_fs_node_id(&psi_id, txn_root, "/A/D/H/psi", pool));
2780     SVN_ERR(check_entry_present(txn_root, "A/D/H", "psi", pool));
2781     SVN_ERR(svn_fs_node_id(&omega_id, txn_root, "/A/D/H/omega", pool));
2782     SVN_ERR(check_entry_present(txn_root, "A/D/H", "omega", pool));
2783     SVN_ERR(svn_fs_node_id(&G_id, txn_root, "/A/D/G", pool));
2784     SVN_ERR(check_entry_present(txn_root, "A/D", "G", pool));
2785     SVN_ERR(svn_fs_node_id(&pi_id, txn_root, "/A/D/G/pi", pool));
2786     SVN_ERR(check_entry_present(txn_root, "A/D/G", "pi", pool));
2787     SVN_ERR(svn_fs_node_id(&rho_id, txn_root, "/A/D/G/rho", pool));
2788     SVN_ERR(check_entry_present(txn_root, "A/D/G", "rho", pool));
2789     SVN_ERR(svn_fs_node_id(&tau_id, txn_root, "/A/D/G/tau", pool));
2790     SVN_ERR(check_entry_present(txn_root, "A/D/G", "tau", pool));
2791     SVN_ERR(svn_fs_node_id(&sigma_id, txn_root, "/A/D/G/sigma", pool));
2792     SVN_ERR(check_entry_present(txn_root, "A/D/G", "sigma", pool));
2793 
2794     /* Delete "A" */
2795     SVN_ERR(svn_fs_delete(txn_root, "A", pool));
2796     SVN_ERR(check_entry_absent(txn_root, "", "A", pool));
2797 
2798     /* Validate the tree.  */
2799     {
2800       static svn_test__tree_entry_t expected_entries[] = {
2801         /* path, contents (0 = dir) */
2802         { "iota",        "This is the file 'iota'.\n" }
2803       };
2804 
2805       SVN_ERR(svn_test__validate_tree(txn_root, expected_entries, 1, pool));
2806     }
2807   }
2808 
2809   /* Abort transaction.  */
2810   SVN_ERR(svn_fs_abort_txn(txn, pool));
2811 
2812   /* 4. Delete immutable file. */
2813 
2814   /* Create new transaction. */
2815   SVN_ERR(svn_fs_begin_txn(&txn, fs, new_rev, pool));
2816   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2817 
2818   {
2819     const svn_fs_id_t *iota_id, *gamma_id;
2820 
2821     /* Check nodes revision ID is present.  */
2822     SVN_ERR(svn_fs_node_id(&iota_id, txn_root, "iota", pool));
2823     SVN_ERR(svn_fs_node_id(&gamma_id, txn_root, "A/D/gamma", pool));
2824     SVN_ERR(check_entry_present(txn_root, "", "iota", pool));
2825     SVN_ERR(check_entry_present(txn_root, "A/D", "gamma", pool));
2826 
2827     /* Delete some files. */
2828     SVN_ERR(svn_fs_delete(txn_root, "iota", pool));
2829     SVN_ERR(svn_fs_delete(txn_root, "A/D/gamma", pool));
2830     SVN_ERR(check_entry_absent(txn_root, "", "iota", pool));
2831     SVN_ERR(check_entry_absent(txn_root, "A/D", "iota", pool));
2832 
2833     /* Validate the tree.  */
2834     {
2835       static svn_test__tree_entry_t expected_entries[] = {
2836         /* path, contents (0 = dir) */
2837         { "A",           0 },
2838         { "A/mu",        "This is the file 'mu'.\n" },
2839         { "A/B",         0 },
2840         { "A/B/lambda",  "This is the file 'lambda'.\n" },
2841         { "A/B/E",       0 },
2842         { "A/B/E/alpha", "This is the file 'alpha'.\n" },
2843         { "A/B/E/beta",  "This is the file 'beta'.\n" },
2844         { "A/B/F",       0 },
2845         { "A/C",         0 },
2846         { "A/D",         0 },
2847         { "A/D/G",       0 },
2848         { "A/D/G/pi",    "This is the file 'pi'.\n" },
2849         { "A/D/G/rho",   "This is the file 'rho'.\n" },
2850         { "A/D/G/tau",   "This is the file 'tau'.\n" },
2851         { "A/D/H",       0 },
2852         { "A/D/H/chi",   "This is the file 'chi'.\n" },
2853         { "A/D/H/psi",   "This is the file 'psi'.\n" },
2854         { "A/D/H/omega", "This is the file 'omega'.\n" }
2855       };
2856       SVN_ERR(svn_test__validate_tree(txn_root, expected_entries, 18, pool));
2857     }
2858   }
2859 
2860   /* Abort transaction.  */
2861   SVN_ERR(svn_fs_abort_txn(txn, pool));
2862 
2863   /* 5. Delete immutable directory. */
2864 
2865   /* Create new transaction. */
2866   SVN_ERR(svn_fs_begin_txn(&txn, fs, new_rev, pool));
2867   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2868 
2869   {
2870     const svn_fs_id_t *A_id, *mu_id, *B_id, *lambda_id, *E_id, *alpha_id,
2871       *beta_id, *F_id, *C_id, *D_id, *gamma_id, *H_id, *chi_id,
2872       *psi_id, *omega_id, *G_id, *pi_id, *rho_id, *tau_id;
2873 
2874     /* Check nodes revision ID is present.  */
2875     SVN_ERR(svn_fs_node_id(&A_id, txn_root, "/A", pool));
2876     SVN_ERR(check_entry_present(txn_root, "", "A", pool));
2877     SVN_ERR(svn_fs_node_id(&mu_id, txn_root, "/A/mu", pool));
2878     SVN_ERR(check_entry_present(txn_root, "A", "mu", pool));
2879     SVN_ERR(svn_fs_node_id(&B_id, txn_root, "/A/B", pool));
2880     SVN_ERR(check_entry_present(txn_root, "A", "B", pool));
2881     SVN_ERR(svn_fs_node_id(&lambda_id, txn_root, "/A/B/lambda", pool));
2882     SVN_ERR(check_entry_present(txn_root, "A/B", "lambda", pool));
2883     SVN_ERR(svn_fs_node_id(&E_id, txn_root, "/A/B/E", pool));
2884     SVN_ERR(check_entry_present(txn_root, "A/B", "E", pool));
2885     SVN_ERR(svn_fs_node_id(&alpha_id, txn_root, "/A/B/E/alpha", pool));
2886     SVN_ERR(check_entry_present(txn_root, "A/B/E", "alpha", pool));
2887     SVN_ERR(svn_fs_node_id(&beta_id, txn_root, "/A/B/E/beta", pool));
2888     SVN_ERR(check_entry_present(txn_root, "A/B/E", "beta", pool));
2889     SVN_ERR(svn_fs_node_id(&F_id, txn_root, "/A/B/F", pool));
2890     SVN_ERR(check_entry_present(txn_root, "A/B", "F", pool));
2891     SVN_ERR(svn_fs_node_id(&C_id, txn_root, "/A/C", pool));
2892     SVN_ERR(check_entry_present(txn_root, "A", "C", pool));
2893     SVN_ERR(svn_fs_node_id(&D_id, txn_root, "/A/D", pool));
2894     SVN_ERR(check_entry_present(txn_root, "A", "D", pool));
2895     SVN_ERR(svn_fs_node_id(&gamma_id, txn_root, "/A/D/gamma", pool));
2896     SVN_ERR(check_entry_present(txn_root, "A/D", "gamma", pool));
2897     SVN_ERR(svn_fs_node_id(&H_id, txn_root, "/A/D/H", pool));
2898     SVN_ERR(check_entry_present(txn_root, "A/D", "H", pool));
2899     SVN_ERR(svn_fs_node_id(&chi_id, txn_root, "/A/D/H/chi", pool));
2900     SVN_ERR(check_entry_present(txn_root, "A/D/H", "chi", pool));
2901     SVN_ERR(svn_fs_node_id(&psi_id, txn_root, "/A/D/H/psi", pool));
2902     SVN_ERR(check_entry_present(txn_root, "A/D/H", "psi", pool));
2903     SVN_ERR(svn_fs_node_id(&omega_id, txn_root, "/A/D/H/omega", pool));
2904     SVN_ERR(check_entry_present(txn_root, "A/D/H", "omega", pool));
2905     SVN_ERR(svn_fs_node_id(&G_id, txn_root, "/A/D/G", pool));
2906     SVN_ERR(check_entry_present(txn_root, "A/D", "G", pool));
2907     SVN_ERR(svn_fs_node_id(&pi_id, txn_root, "/A/D/G/pi", pool));
2908     SVN_ERR(check_entry_present(txn_root, "A/D/G", "pi", pool));
2909     SVN_ERR(svn_fs_node_id(&rho_id, txn_root, "/A/D/G/rho", pool));
2910     SVN_ERR(check_entry_present(txn_root, "A/D/G", "rho", pool));
2911     SVN_ERR(svn_fs_node_id(&tau_id, txn_root, "/A/D/G/tau", pool));
2912     SVN_ERR(check_entry_present(txn_root, "A/D/G", "tau", pool));
2913 
2914     /* Delete "A" */
2915     SVN_ERR(svn_fs_delete(txn_root, "A", pool));
2916     SVN_ERR(check_entry_absent(txn_root, "", "A", pool));
2917 
2918     /* Validate the tree.  */
2919     {
2920       static svn_test__tree_entry_t expected_entries[] = {
2921         /* path, contents (0 = dir) */
2922         { "iota",        "This is the file 'iota'.\n" }
2923       };
2924       SVN_ERR(svn_test__validate_tree(txn_root, expected_entries, 1, pool));
2925     }
2926   }
2927 
2928   return SVN_NO_ERROR;
2929 }
2930 
2931 
2932 
2933 /* Test the datestamps on commits. */
2934 static svn_error_t *
commit_date(const svn_test_opts_t * opts,apr_pool_t * pool)2935 commit_date(const svn_test_opts_t *opts,
2936             apr_pool_t *pool)
2937 {
2938   svn_fs_t *fs;
2939   svn_fs_txn_t *txn;
2940   svn_fs_root_t *txn_root;
2941   svn_revnum_t rev;
2942   svn_string_t *datestamp;
2943   apr_time_t before_commit, at_commit, after_commit;
2944 
2945   /* Prepare a filesystem. */
2946   SVN_ERR(svn_test__create_fs(&fs, "test-repo-commit-date",
2947                               opts, pool));
2948 
2949   before_commit = apr_time_now();
2950 
2951   /* Commit a greek tree. */
2952   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
2953   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
2954   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
2955   SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool));
2956   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(rev));
2957 
2958   after_commit = apr_time_now();
2959 
2960   /* Get the datestamp of the commit. */
2961   SVN_ERR(svn_fs_revision_prop(&datestamp, fs, rev, SVN_PROP_REVISION_DATE,
2962                                pool));
2963 
2964   if (datestamp == NULL)
2965     return svn_error_create
2966       (SVN_ERR_FS_GENERAL, NULL,
2967        "failed to get datestamp of committed revision");
2968 
2969   SVN_ERR(svn_time_from_cstring(&at_commit, datestamp->data, pool));
2970 
2971   if (at_commit < before_commit)
2972     return svn_error_create
2973       (SVN_ERR_FS_GENERAL, NULL,
2974        "datestamp too early");
2975 
2976   if (at_commit > after_commit)
2977     return svn_error_create
2978       (SVN_ERR_FS_GENERAL, NULL,
2979        "datestamp too late");
2980 
2981   return SVN_NO_ERROR;
2982 }
2983 
2984 
2985 static svn_error_t *
check_old_revisions(const svn_test_opts_t * opts,apr_pool_t * pool)2986 check_old_revisions(const svn_test_opts_t *opts,
2987                     apr_pool_t *pool)
2988 {
2989   svn_fs_t *fs;
2990   svn_fs_txn_t *txn;
2991   svn_fs_root_t *txn_root;
2992   svn_revnum_t rev;
2993   apr_pool_t *subpool = svn_pool_create(pool);
2994 
2995   /* Prepare a filesystem. */
2996   SVN_ERR(svn_test__create_fs(&fs, "test-repo-check-old-revisions",
2997                               opts, pool));
2998 
2999   /* Commit a greek tree. */
3000   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
3001   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3002   SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
3003   SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, subpool));
3004   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(rev));
3005   svn_pool_clear(subpool);
3006 
3007   /* Modify and commit iota a few times, then test to see if we can
3008      retrieve all the committed revisions. */
3009   {
3010     /* right-side numbers match revision numbers */
3011 #define iota_contents_1 "This is the file 'iota'.\n"
3012 
3013     /* Add a char to the front. */
3014 #define iota_contents_2 "XThis is the file 'iota'.\n"
3015 
3016     /* Add a char to the end. */
3017 #define iota_contents_3 "XThis is the file 'iota'.\nX"
3018 
3019     /* Add a couple of chars in the middle. */
3020 #define iota_contents_4 "XThis is the X file 'iota'.\nX"
3021 
3022     /* Randomly add and delete chars all over. */
3023 #define iota_contents_5 \
3024     "XTYhQis is ACK, PHHHT! no longer 'ioZZZZZta'.blarf\nbye"
3025 
3026     /* Reassure iota that it will live for quite some time. */
3027 #define iota_contents_6 "Matthew 5:18 (Revised Standard Version) --\n\
3028 For truly, I say to you, till heaven and earth pass away, not an iota,\n\
3029 not a dot, will pass from the law until all is accomplished."
3030 
3031     /* Revert to the original contents. */
3032 #define iota_contents_7 "This is the file 'iota'.\n"
3033 
3034     /* Revision 2. */
3035     SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, subpool));
3036     SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3037     SVN_ERR(svn_test__set_file_contents
3038             (txn_root, "iota", iota_contents_2, subpool));
3039     SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, subpool));
3040     SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(rev));
3041     svn_pool_clear(subpool);
3042 
3043     /* Revision 3. */
3044     SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, subpool));
3045     SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3046     SVN_ERR(svn_test__set_file_contents
3047             (txn_root, "iota", iota_contents_3, subpool));
3048     SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, subpool));
3049     SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(rev));
3050     svn_pool_clear(subpool);
3051 
3052     /* Revision 4. */
3053     SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, subpool));
3054     SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3055     SVN_ERR(svn_test__set_file_contents
3056             (txn_root, "iota", iota_contents_4, subpool));
3057     SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, subpool));
3058     SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(rev));
3059     svn_pool_clear(subpool);
3060 
3061     /* Revision 5. */
3062     SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, subpool));
3063     SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3064     SVN_ERR(svn_test__set_file_contents
3065             (txn_root, "iota", iota_contents_5, subpool));
3066     SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, subpool));
3067     SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(rev));
3068     svn_pool_clear(subpool);
3069 
3070     /* Revision 6. */
3071     SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, subpool));
3072     SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3073     SVN_ERR(svn_test__set_file_contents
3074             (txn_root, "iota", iota_contents_6, subpool));
3075     SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, subpool));
3076     SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(rev));
3077     svn_pool_clear(subpool);
3078 
3079     /* Revision 7. */
3080     SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, subpool));
3081     SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3082     SVN_ERR(svn_test__set_file_contents
3083             (txn_root, "iota", iota_contents_7, subpool));
3084     SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, subpool));
3085     SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(rev));
3086     svn_pool_clear(subpool);
3087 
3088     /** Now check the full Greek Tree in all of those revisions,
3089         adjusting `iota' for each one. ***/
3090 
3091     /* Validate revision 1.  */
3092     {
3093       svn_fs_root_t *root;
3094       static svn_test__tree_entry_t expected_entries[] = {
3095         /* path, contents (0 = dir) */
3096         { "iota",        iota_contents_1 },
3097         { "A",           0 },
3098         { "A/mu",        "This is the file 'mu'.\n" },
3099         { "A/B",         0 },
3100         { "A/B/lambda",  "This is the file 'lambda'.\n" },
3101         { "A/B/E",       0 },
3102         { "A/B/E/alpha", "This is the file 'alpha'.\n" },
3103         { "A/B/E/beta",  "This is the file 'beta'.\n" },
3104         { "A/B/F",       0 },
3105         { "A/C",         0 },
3106         { "A/D",         0 },
3107         { "A/D/gamma",   "This is the file 'gamma'.\n" },
3108         { "A/D/G",       0 },
3109         { "A/D/G/pi",    "This is the file 'pi'.\n" },
3110         { "A/D/G/rho",   "This is the file 'rho'.\n" },
3111         { "A/D/G/tau",   "This is the file 'tau'.\n" },
3112         { "A/D/H",       0 },
3113         { "A/D/H/chi",   "This is the file 'chi'.\n" },
3114         { "A/D/H/psi",   "This is the file 'psi'.\n" },
3115         { "A/D/H/omega", "This is the file 'omega'.\n" }
3116       };
3117 
3118       SVN_ERR(svn_fs_revision_root(&root, fs, 1, pool));
3119       SVN_ERR(svn_test__validate_tree(root, expected_entries, 20, pool));
3120     }
3121 
3122     /* Validate revision 2.  */
3123     {
3124       svn_fs_root_t *root;
3125       static svn_test__tree_entry_t expected_entries[] = {
3126         /* path, contents (0 = dir) */
3127         { "iota",        iota_contents_2 },
3128         { "A",           0 },
3129         { "A/mu",        "This is the file 'mu'.\n" },
3130         { "A/B",         0 },
3131         { "A/B/lambda",  "This is the file 'lambda'.\n" },
3132         { "A/B/E",       0 },
3133         { "A/B/E/alpha", "This is the file 'alpha'.\n" },
3134         { "A/B/E/beta",  "This is the file 'beta'.\n" },
3135         { "A/B/F",       0 },
3136         { "A/C",         0 },
3137         { "A/D",         0 },
3138         { "A/D/gamma",   "This is the file 'gamma'.\n" },
3139         { "A/D/G",       0 },
3140         { "A/D/G/pi",    "This is the file 'pi'.\n" },
3141         { "A/D/G/rho",   "This is the file 'rho'.\n" },
3142         { "A/D/G/tau",   "This is the file 'tau'.\n" },
3143         { "A/D/H",       0 },
3144         { "A/D/H/chi",   "This is the file 'chi'.\n" },
3145         { "A/D/H/psi",   "This is the file 'psi'.\n" },
3146         { "A/D/H/omega", "This is the file 'omega'.\n" }
3147       };
3148 
3149       SVN_ERR(svn_fs_revision_root(&root, fs, 2, pool));
3150       SVN_ERR(svn_test__validate_tree(root, expected_entries, 20, pool));
3151     }
3152 
3153     /* Validate revision 3.  */
3154     {
3155       svn_fs_root_t *root;
3156       static svn_test__tree_entry_t expected_entries[] = {
3157         /* path, contents (0 = dir) */
3158         { "iota",        iota_contents_3 },
3159         { "A",           0 },
3160         { "A/mu",        "This is the file 'mu'.\n" },
3161         { "A/B",         0 },
3162         { "A/B/lambda",  "This is the file 'lambda'.\n" },
3163         { "A/B/E",       0 },
3164         { "A/B/E/alpha", "This is the file 'alpha'.\n" },
3165         { "A/B/E/beta",  "This is the file 'beta'.\n" },
3166         { "A/B/F",       0 },
3167         { "A/C",         0 },
3168         { "A/D",         0 },
3169         { "A/D/gamma",   "This is the file 'gamma'.\n" },
3170         { "A/D/G",       0 },
3171         { "A/D/G/pi",    "This is the file 'pi'.\n" },
3172         { "A/D/G/rho",   "This is the file 'rho'.\n" },
3173         { "A/D/G/tau",   "This is the file 'tau'.\n" },
3174         { "A/D/H",       0 },
3175         { "A/D/H/chi",   "This is the file 'chi'.\n" },
3176         { "A/D/H/psi",   "This is the file 'psi'.\n" },
3177         { "A/D/H/omega", "This is the file 'omega'.\n" }
3178       };
3179 
3180       SVN_ERR(svn_fs_revision_root(&root, fs, 3, pool));
3181       SVN_ERR(svn_test__validate_tree(root, expected_entries, 20, pool));
3182     }
3183 
3184     /* Validate revision 4.  */
3185     {
3186       svn_fs_root_t *root;
3187       static svn_test__tree_entry_t expected_entries[] = {
3188         /* path, contents (0 = dir) */
3189         { "iota",        iota_contents_4 },
3190         { "A",           0 },
3191         { "A/mu",        "This is the file 'mu'.\n" },
3192         { "A/B",         0 },
3193         { "A/B/lambda",  "This is the file 'lambda'.\n" },
3194         { "A/B/E",       0 },
3195         { "A/B/E/alpha", "This is the file 'alpha'.\n" },
3196         { "A/B/E/beta",  "This is the file 'beta'.\n" },
3197         { "A/B/F",       0 },
3198         { "A/C",         0 },
3199         { "A/D",         0 },
3200         { "A/D/gamma",   "This is the file 'gamma'.\n" },
3201         { "A/D/G",       0 },
3202         { "A/D/G/pi",    "This is the file 'pi'.\n" },
3203         { "A/D/G/rho",   "This is the file 'rho'.\n" },
3204         { "A/D/G/tau",   "This is the file 'tau'.\n" },
3205         { "A/D/H",       0 },
3206         { "A/D/H/chi",   "This is the file 'chi'.\n" },
3207         { "A/D/H/psi",   "This is the file 'psi'.\n" },
3208         { "A/D/H/omega", "This is the file 'omega'.\n" }
3209       };
3210 
3211       SVN_ERR(svn_fs_revision_root(&root, fs, 4, pool));
3212       SVN_ERR(svn_test__validate_tree(root, expected_entries, 20, pool));
3213     }
3214 
3215     /* Validate revision 5.  */
3216     {
3217       svn_fs_root_t *root;
3218       static svn_test__tree_entry_t expected_entries[] = {
3219         /* path, contents (0 = dir) */
3220         { "iota",        iota_contents_5 },
3221         { "A",           0 },
3222         { "A/mu",        "This is the file 'mu'.\n" },
3223         { "A/B",         0 },
3224         { "A/B/lambda",  "This is the file 'lambda'.\n" },
3225         { "A/B/E",       0 },
3226         { "A/B/E/alpha", "This is the file 'alpha'.\n" },
3227         { "A/B/E/beta",  "This is the file 'beta'.\n" },
3228         { "A/B/F",       0 },
3229         { "A/C",         0 },
3230         { "A/D",         0 },
3231         { "A/D/G",       0 },
3232         { "A/D/gamma",   "This is the file 'gamma'.\n" },
3233         { "A/D/G/pi",    "This is the file 'pi'.\n" },
3234         { "A/D/G/rho",   "This is the file 'rho'.\n" },
3235         { "A/D/G/tau",   "This is the file 'tau'.\n" },
3236         { "A/D/H",       0 },
3237         { "A/D/H/chi",   "This is the file 'chi'.\n" },
3238         { "A/D/H/psi",   "This is the file 'psi'.\n" },
3239         { "A/D/H/omega", "This is the file 'omega'.\n" }
3240       };
3241 
3242       SVN_ERR(svn_fs_revision_root(&root, fs, 5, pool));
3243       SVN_ERR(svn_test__validate_tree(root, expected_entries, 20, pool));
3244     }
3245 
3246     /* Validate revision 6.  */
3247     {
3248       svn_fs_root_t *root;
3249       static svn_test__tree_entry_t expected_entries[] = {
3250         /* path, contents (0 = dir) */
3251         { "iota",        iota_contents_6 },
3252         { "A",           0 },
3253         { "A/mu",        "This is the file 'mu'.\n" },
3254         { "A/B",         0 },
3255         { "A/B/lambda",  "This is the file 'lambda'.\n" },
3256         { "A/B/E",       0 },
3257         { "A/B/E/alpha", "This is the file 'alpha'.\n" },
3258         { "A/B/E/beta",  "This is the file 'beta'.\n" },
3259         { "A/B/F",       0 },
3260         { "A/C",         0 },
3261         { "A/D",         0 },
3262         { "A/D/gamma",   "This is the file 'gamma'.\n" },
3263         { "A/D/G",       0 },
3264         { "A/D/G/pi",    "This is the file 'pi'.\n" },
3265         { "A/D/G/rho",   "This is the file 'rho'.\n" },
3266         { "A/D/G/tau",   "This is the file 'tau'.\n" },
3267         { "A/D/H",       0 },
3268         { "A/D/H/chi",   "This is the file 'chi'.\n" },
3269         { "A/D/H/psi",   "This is the file 'psi'.\n" },
3270         { "A/D/H/omega", "This is the file 'omega'.\n" }
3271       };
3272 
3273       SVN_ERR(svn_fs_revision_root(&root, fs, 6, pool));
3274       SVN_ERR(svn_test__validate_tree(root, expected_entries, 20, pool));
3275     }
3276 
3277     /* Validate revision 7.  */
3278     {
3279       svn_fs_root_t *root;
3280       static svn_test__tree_entry_t expected_entries[] = {
3281         /* path, contents (0 = dir) */
3282         { "iota",        iota_contents_7 },
3283         { "A",           0 },
3284         { "A/mu",        "This is the file 'mu'.\n" },
3285         { "A/B",         0 },
3286         { "A/B/lambda",  "This is the file 'lambda'.\n" },
3287         { "A/B/E",       0 },
3288         { "A/B/E/alpha", "This is the file 'alpha'.\n" },
3289         { "A/B/E/beta",  "This is the file 'beta'.\n" },
3290         { "A/B/F",       0 },
3291         { "A/C",         0 },
3292         { "A/D",         0 },
3293         { "A/D/gamma",   "This is the file 'gamma'.\n" },
3294         { "A/D/G",       0 },
3295         { "A/D/G/pi",    "This is the file 'pi'.\n" },
3296         { "A/D/G/rho",   "This is the file 'rho'.\n" },
3297         { "A/D/G/tau",   "This is the file 'tau'.\n" },
3298         { "A/D/H",       0 },
3299         { "A/D/H/chi",   "This is the file 'chi'.\n" },
3300         { "A/D/H/psi",   "This is the file 'psi'.\n" },
3301         { "A/D/H/omega", "This is the file 'omega'.\n" }
3302       };
3303 
3304       SVN_ERR(svn_fs_revision_root(&root, fs, 7, pool));
3305       SVN_ERR(svn_test__validate_tree(root, expected_entries, 20, pool));
3306     }
3307   }
3308 
3309   svn_pool_destroy(subpool);
3310   return SVN_NO_ERROR;
3311 }
3312 
3313 
3314 /* For each revision R in FS, from 0 to MAX_REV, check that it
3315    matches the tree in EXPECTED_TREES[R].  Use POOL for any
3316    allocations.  This is a helper function for check_all_revisions. */
3317 static svn_error_t *
validate_revisions(svn_fs_t * fs,svn_test__tree_t * expected_trees,svn_revnum_t max_rev,apr_pool_t * pool)3318 validate_revisions(svn_fs_t *fs,
3319                    svn_test__tree_t *expected_trees,
3320                    svn_revnum_t max_rev,
3321                    apr_pool_t *pool)
3322 {
3323   svn_fs_root_t *revision_root;
3324   svn_revnum_t i;
3325   svn_error_t *err;
3326   apr_pool_t *subpool = svn_pool_create(pool);
3327 
3328   /* Validate all revisions up to the current one. */
3329   for (i = 0; i <= max_rev; i++)
3330     {
3331       SVN_ERR(svn_fs_revision_root(&revision_root, fs,
3332                                    (svn_revnum_t)i, subpool));
3333       err = svn_test__validate_tree(revision_root,
3334                                     expected_trees[i].entries,
3335                                     expected_trees[i].num_entries,
3336                                     subpool);
3337       if (err)
3338         return svn_error_createf
3339           (SVN_ERR_FS_GENERAL, err,
3340            "Error validating revision %ld (youngest is %ld)", i, max_rev);
3341       svn_pool_clear(subpool);
3342     }
3343 
3344   svn_pool_destroy(subpool);
3345   return SVN_NO_ERROR;
3346 }
3347 
3348 
3349 static svn_error_t *
check_all_revisions(const svn_test_opts_t * opts,apr_pool_t * pool)3350 check_all_revisions(const svn_test_opts_t *opts,
3351                     apr_pool_t *pool)
3352 {
3353   svn_fs_t *fs;
3354   svn_fs_txn_t *txn;
3355   svn_fs_root_t *txn_root;
3356   svn_revnum_t youngest_rev;
3357   svn_test__tree_t expected_trees[5]; /* one tree per commit, please */
3358   svn_revnum_t revision_count = 0;
3359   apr_pool_t *subpool = svn_pool_create(pool);
3360 
3361   /* Create a filesystem and repository. */
3362   SVN_ERR(svn_test__create_fs(&fs, "test-repo-check-all-revisions",
3363                               opts, pool));
3364 
3365   /***********************************************************************/
3366   /* REVISION 0 */
3367   /***********************************************************************/
3368   {
3369     expected_trees[revision_count].num_entries = 0;
3370     expected_trees[revision_count].entries = 0;
3371     SVN_ERR(validate_revisions(fs, expected_trees, revision_count, subpool));
3372     revision_count++;
3373   }
3374   svn_pool_clear(subpool);
3375 
3376   /* Create and commit the greek tree. */
3377   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
3378   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3379   SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
3380   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
3381   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
3382 
3383   /***********************************************************************/
3384   /* REVISION 1 */
3385   /***********************************************************************/
3386   {
3387     static svn_test__tree_entry_t expected_entries[] = {
3388       /* path, contents (0 = dir) */
3389       { "iota",        "This is the file 'iota'.\n" },
3390       { "A",           0 },
3391       { "A/mu",        "This is the file 'mu'.\n" },
3392       { "A/B",         0 },
3393       { "A/B/lambda",  "This is the file 'lambda'.\n" },
3394       { "A/B/E",       0 },
3395       { "A/B/E/alpha", "This is the file 'alpha'.\n" },
3396       { "A/B/E/beta",  "This is the file 'beta'.\n" },
3397       { "A/B/F",       0 },
3398       { "A/C",         0 },
3399       { "A/D",         0 },
3400       { "A/D/gamma",   "This is the file 'gamma'.\n" },
3401       { "A/D/G",       0 },
3402       { "A/D/G/pi",    "This is the file 'pi'.\n" },
3403       { "A/D/G/rho",   "This is the file 'rho'.\n" },
3404       { "A/D/G/tau",   "This is the file 'tau'.\n" },
3405       { "A/D/H",       0 },
3406       { "A/D/H/chi",   "This is the file 'chi'.\n" },
3407       { "A/D/H/psi",   "This is the file 'psi'.\n" },
3408       { "A/D/H/omega", "This is the file 'omega'.\n" }
3409     };
3410     expected_trees[revision_count].entries = expected_entries;
3411     expected_trees[revision_count].num_entries = 20;
3412     SVN_ERR(validate_revisions(fs, expected_trees, revision_count, subpool));
3413     revision_count++;
3414   }
3415   svn_pool_clear(subpool);
3416 
3417   /* Make a new txn based on the youngest revision, make some changes,
3418      and commit those changes (which makes a new youngest
3419      revision). */
3420   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
3421   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3422   {
3423     static svn_test__txn_script_command_t script_entries[] = {
3424       { 'a', "A/delta",     "This is the file 'delta'.\n" },
3425       { 'a', "A/epsilon",   "This is the file 'epsilon'.\n" },
3426       { 'a', "A/B/Z",       0 },
3427       { 'a', "A/B/Z/zeta",  "This is the file 'zeta'.\n" },
3428       { 'd', "A/C",         0 },
3429       { 'd', "A/mu",        "" },
3430       { 'd', "A/D/G/tau",   "" },
3431       { 'd', "A/D/H/omega", "" },
3432       { 'e', "iota",        "Changed file 'iota'.\n" },
3433       { 'e', "A/D/G/rho",   "Changed file 'rho'.\n" }
3434     };
3435     SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 10,
3436                                       subpool));
3437   }
3438   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
3439   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
3440 
3441   /***********************************************************************/
3442   /* REVISION 2 */
3443   /***********************************************************************/
3444   {
3445     static svn_test__tree_entry_t expected_entries[] = {
3446       /* path, contents (0 = dir) */
3447       { "iota",        "Changed file 'iota'.\n" },
3448       { "A",           0 },
3449       { "A/delta",     "This is the file 'delta'.\n" },
3450       { "A/epsilon",   "This is the file 'epsilon'.\n" },
3451       { "A/B",         0 },
3452       { "A/B/lambda",  "This is the file 'lambda'.\n" },
3453       { "A/B/E",       0 },
3454       { "A/B/E/alpha", "This is the file 'alpha'.\n" },
3455       { "A/B/E/beta",  "This is the file 'beta'.\n" },
3456       { "A/B/F",       0 },
3457       { "A/B/Z",       0 },
3458       { "A/B/Z/zeta",  "This is the file 'zeta'.\n" },
3459       { "A/D",         0 },
3460       { "A/D/gamma",   "This is the file 'gamma'.\n" },
3461       { "A/D/G",       0 },
3462       { "A/D/G/pi",    "This is the file 'pi'.\n" },
3463       { "A/D/G/rho",   "Changed file 'rho'.\n" },
3464       { "A/D/H",       0 },
3465       { "A/D/H/chi",   "This is the file 'chi'.\n" },
3466       { "A/D/H/psi",   "This is the file 'psi'.\n" }
3467     };
3468     expected_trees[revision_count].entries = expected_entries;
3469     expected_trees[revision_count].num_entries = 20;
3470     SVN_ERR(validate_revisions(fs, expected_trees, revision_count, subpool));
3471     revision_count++;
3472   }
3473   svn_pool_clear(subpool);
3474 
3475   /* Make a new txn based on the youngest revision, make some changes,
3476      and commit those changes (which makes a new youngest
3477      revision). */
3478   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
3479   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3480   {
3481     static svn_test__txn_script_command_t script_entries[] = {
3482       { 'a', "A/mu",        "Re-added file 'mu'.\n" },
3483       { 'a', "A/D/H/omega", 0 }, /* re-add omega as directory! */
3484       { 'd', "iota",        "" },
3485       { 'e', "A/delta",     "This is the file 'delta'.\nLine 2.\n" }
3486     };
3487     SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 4,
3488                                       subpool));
3489   }
3490   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
3491   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
3492 
3493   /***********************************************************************/
3494   /* REVISION 3 */
3495   /***********************************************************************/
3496   {
3497     static svn_test__tree_entry_t expected_entries[] = {
3498       /* path, contents (0 = dir) */
3499       { "A",           0 },
3500       { "A/delta",     "This is the file 'delta'.\nLine 2.\n" },
3501       { "A/epsilon",   "This is the file 'epsilon'.\n" },
3502       { "A/mu",        "Re-added file 'mu'.\n" },
3503       { "A/B",         0 },
3504       { "A/B/lambda",  "This is the file 'lambda'.\n" },
3505       { "A/B/E",       0 },
3506       { "A/B/E/alpha", "This is the file 'alpha'.\n" },
3507       { "A/B/E/beta",  "This is the file 'beta'.\n" },
3508       { "A/B/F",       0 },
3509       { "A/B/Z",       0 },
3510       { "A/B/Z/zeta",  "This is the file 'zeta'.\n" },
3511       { "A/D",         0 },
3512       { "A/D/gamma",   "This is the file 'gamma'.\n" },
3513       { "A/D/G",       0 },
3514       { "A/D/G/pi",    "This is the file 'pi'.\n" },
3515       { "A/D/G/rho",   "Changed file 'rho'.\n" },
3516       { "A/D/H",       0 },
3517       { "A/D/H/chi",   "This is the file 'chi'.\n" },
3518       { "A/D/H/psi",   "This is the file 'psi'.\n" },
3519       { "A/D/H/omega", 0 }
3520     };
3521     expected_trees[revision_count].entries = expected_entries;
3522     expected_trees[revision_count].num_entries = 21;
3523     SVN_ERR(validate_revisions(fs, expected_trees, revision_count, subpool));
3524     revision_count++;
3525   }
3526   svn_pool_clear(subpool);
3527 
3528   /* Make a new txn based on the youngest revision, make some changes,
3529      and commit those changes (which makes a new youngest
3530      revision). */
3531   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
3532   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3533   {
3534     static svn_test__txn_script_command_t script_entries[] = {
3535       { 'c', "A/D/G",        "A/D/G2" },
3536       { 'c', "A/epsilon",    "A/B/epsilon" },
3537     };
3538     SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 2, subpool));
3539   }
3540   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
3541   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
3542 
3543   /***********************************************************************/
3544   /* REVISION 4 */
3545   /***********************************************************************/
3546   {
3547     static svn_test__tree_entry_t expected_entries[] = {
3548       /* path, contents (0 = dir) */
3549       { "A",           0 },
3550       { "A/delta",     "This is the file 'delta'.\nLine 2.\n" },
3551       { "A/epsilon",   "This is the file 'epsilon'.\n" },
3552       { "A/mu",        "Re-added file 'mu'.\n" },
3553       { "A/B",         0 },
3554       { "A/B/epsilon", "This is the file 'epsilon'.\n" },
3555       { "A/B/lambda",  "This is the file 'lambda'.\n" },
3556       { "A/B/E",       0 },
3557       { "A/B/E/alpha", "This is the file 'alpha'.\n" },
3558       { "A/B/E/beta",  "This is the file 'beta'.\n" },
3559       { "A/B/F",       0 },
3560       { "A/B/Z",       0 },
3561       { "A/B/Z/zeta",  "This is the file 'zeta'.\n" },
3562       { "A/D",         0 },
3563       { "A/D/gamma",   "This is the file 'gamma'.\n" },
3564       { "A/D/G",       0 },
3565       { "A/D/G/pi",    "This is the file 'pi'.\n" },
3566       { "A/D/G/rho",   "Changed file 'rho'.\n" },
3567       { "A/D/G2",      0 },
3568       { "A/D/G2/pi",   "This is the file 'pi'.\n" },
3569       { "A/D/G2/rho",  "Changed file 'rho'.\n" },
3570       { "A/D/H",       0 },
3571       { "A/D/H/chi",   "This is the file 'chi'.\n" },
3572       { "A/D/H/psi",   "This is the file 'psi'.\n" },
3573       { "A/D/H/omega", 0 }
3574     };
3575     expected_trees[revision_count].entries = expected_entries;
3576     expected_trees[revision_count].num_entries = 25;
3577     SVN_ERR(validate_revisions(fs, expected_trees, revision_count, subpool));
3578     revision_count++;
3579   }
3580   svn_pool_destroy(subpool);
3581 
3582   return SVN_NO_ERROR;
3583 }
3584 
3585 
3586 /* Helper function for large_file_integrity().  Given a ROOT and PATH
3587    to a file, set *CHECKSUM to the checksum of kind CHECKSUM_KIND for the
3588    contents of the file. */
3589 static svn_error_t *
get_file_checksum(svn_checksum_t ** checksum,svn_checksum_kind_t checksum_kind,svn_fs_root_t * root,const char * path,apr_pool_t * pool)3590 get_file_checksum(svn_checksum_t **checksum,
3591                   svn_checksum_kind_t checksum_kind,
3592                   svn_fs_root_t *root,
3593                   const char *path,
3594                   apr_pool_t *pool)
3595 {
3596   svn_stream_t *stream;
3597 
3598   /* Get a stream for the file contents. */
3599   SVN_ERR(svn_fs_file_contents(&stream, root, path, pool));
3600   SVN_ERR(svn_stream_contents_checksum(checksum, stream, checksum_kind,
3601                                        pool, pool));
3602 
3603   return SVN_NO_ERROR;
3604 }
3605 
3606 
3607 /* Return a pseudo-random number in the range [0,SCALAR) i.e. return
3608    a number N such that 0 <= N < SCALAR */
my_rand(apr_uint64_t scalar,apr_uint32_t * seed)3609 static int my_rand(apr_uint64_t scalar, apr_uint32_t *seed)
3610 {
3611   static const apr_uint32_t TEST_RAND_MAX = 0xffffffffUL;
3612   /* Assumes TEST_RAND_MAX+1 can be exactly represented in a double */
3613   apr_uint32_t r = svn_test_rand(seed);
3614   return (int)(((double)r
3615                 / ((double)TEST_RAND_MAX+1.0))
3616                * (double)scalar);
3617 }
3618 
3619 
3620 /* Put pseudo-random bytes in buffer BUF (which is LEN bytes long).
3621    If FULL is TRUE, simply replace every byte in BUF with a
3622    pseudo-random byte, else, replace a pseudo-random collection of
3623    bytes with pseudo-random data. */
3624 static void
random_data_to_buffer(char * buf,apr_size_t buf_len,svn_boolean_t full,apr_uint32_t * seed)3625 random_data_to_buffer(char *buf,
3626                       apr_size_t buf_len,
3627                       svn_boolean_t full,
3628                       apr_uint32_t *seed)
3629 {
3630   apr_size_t i;
3631   apr_size_t num_bytes;
3632   apr_size_t offset;
3633 
3634   int ds_off = 0;
3635   const char *dataset = "0123456789";
3636   apr_size_t dataset_size = strlen(dataset);
3637 
3638   if (full)
3639     {
3640       for (i = 0; i < buf_len; i++)
3641         {
3642           ds_off = my_rand(dataset_size, seed);
3643           buf[i] = dataset[ds_off];
3644         }
3645 
3646       return;
3647     }
3648 
3649   num_bytes = my_rand(buf_len / 100, seed) + 1;
3650   for (i = 0; i < num_bytes; i++)
3651     {
3652       offset = my_rand(buf_len - 1, seed);
3653       ds_off = my_rand(dataset_size, seed);
3654       buf[offset] = dataset[ds_off];
3655     }
3656 
3657   return;
3658 }
3659 
3660 
3661 static svn_error_t *
file_integrity_helper(apr_size_t filesize,apr_uint32_t * seed,const svn_test_opts_t * opts,const char * fs_name,apr_pool_t * pool)3662 file_integrity_helper(apr_size_t filesize, apr_uint32_t *seed,
3663                       const svn_test_opts_t *opts, const char *fs_name,
3664                       apr_pool_t *pool)
3665 {
3666   svn_fs_t *fs;
3667   svn_fs_txn_t *txn;
3668   svn_fs_root_t *txn_root, *rev_root;
3669   svn_revnum_t youngest_rev = 0;
3670   apr_pool_t *subpool = svn_pool_create(pool);
3671   svn_string_t contents;
3672   char *content_buffer;
3673   svn_checksum_t *checksum;
3674   svn_checksum_kind_t checksum_kind = svn_checksum_md5;
3675   svn_checksum_t *checksum_list[100];
3676   svn_txdelta_window_handler_t wh_func;
3677   void *wh_baton;
3678   svn_revnum_t j;
3679 
3680   /* Create a filesystem and repository. */
3681   SVN_ERR(svn_test__create_fs(&fs, fs_name, opts, pool));
3682 
3683   /* Set up our file contents string buffer. */
3684   content_buffer = apr_palloc(pool, filesize);
3685 
3686   contents.data = content_buffer;
3687   contents.len = filesize;
3688 
3689   /* THE PLAN:
3690 
3691      The plan here is simple.  We have a very large file (FILESIZE
3692      bytes) that we initialize with pseudo-random data and commit.
3693      Then we make pseudo-random modifications to that file's contents,
3694      committing after each mod.  Prior to each commit, we generate an
3695      MD5 checksum for the contents of the file, storing each of those
3696      checksums in an array.  After we've made a whole bunch of edits
3697      and commits, we'll re-check that file's contents as of each
3698      revision in the repository, recalculate a checksum for those
3699      contents, and make sure the "before" and "after" checksums
3700      match.  */
3701 
3702   /* Create a big, ugly, pseudo-random-filled file and commit it.  */
3703   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
3704   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3705   SVN_ERR(svn_fs_make_file(txn_root, "bigfile", subpool));
3706   random_data_to_buffer(content_buffer, filesize, TRUE, seed);
3707   SVN_ERR(svn_checksum(&checksum, checksum_kind, contents.data, contents.len,
3708                        pool));
3709   SVN_ERR(svn_fs_apply_textdelta
3710           (&wh_func, &wh_baton, txn_root, "bigfile", NULL, NULL, subpool));
3711   SVN_ERR(svn_txdelta_send_string(&contents, wh_func, wh_baton, subpool));
3712   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
3713   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
3714   SVN_ERR(svn_fs_deltify_revision(fs, youngest_rev, subpool));
3715   checksum_list[youngest_rev] = checksum;
3716   svn_pool_clear(subpool);
3717 
3718   /* Now, let's make some edits to the beginning of our file, and
3719      commit those. */
3720   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
3721   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3722   random_data_to_buffer(content_buffer, 20, TRUE, seed);
3723   SVN_ERR(svn_checksum(&checksum, checksum_kind, contents.data, contents.len,
3724                        pool));
3725   SVN_ERR(svn_fs_apply_textdelta
3726           (&wh_func, &wh_baton, txn_root, "bigfile", NULL, NULL, subpool));
3727   SVN_ERR(svn_txdelta_send_string(&contents, wh_func, wh_baton, subpool));
3728   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
3729   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
3730   SVN_ERR(svn_fs_deltify_revision(fs, youngest_rev, subpool));
3731   checksum_list[youngest_rev] = checksum;
3732   svn_pool_clear(subpool);
3733 
3734   /* Now, let's make some edits to the end of our file. */
3735   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
3736   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3737   random_data_to_buffer(content_buffer + (filesize - 20), 20, TRUE, seed);
3738   SVN_ERR(svn_checksum(&checksum, checksum_kind, contents.data, contents.len,
3739                        pool));
3740   SVN_ERR(svn_fs_apply_textdelta
3741           (&wh_func, &wh_baton, txn_root, "bigfile", NULL, NULL, subpool));
3742   SVN_ERR(svn_txdelta_send_string(&contents, wh_func, wh_baton, subpool));
3743   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
3744   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
3745   SVN_ERR(svn_fs_deltify_revision(fs, youngest_rev, subpool));
3746   checksum_list[youngest_rev] = checksum;
3747   svn_pool_clear(subpool);
3748 
3749   /* How about some edits to both the beginning and the end of the
3750      file? */
3751   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
3752   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3753   random_data_to_buffer(content_buffer, 20, TRUE, seed);
3754   random_data_to_buffer(content_buffer + (filesize - 20), 20, TRUE, seed);
3755   SVN_ERR(svn_checksum(&checksum, checksum_kind, contents.data, contents.len,
3756                        pool));
3757   SVN_ERR(svn_fs_apply_textdelta
3758           (&wh_func, &wh_baton, txn_root, "bigfile", NULL, NULL, subpool));
3759   SVN_ERR(svn_txdelta_send_string(&contents, wh_func, wh_baton, subpool));
3760   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
3761   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
3762   SVN_ERR(svn_fs_deltify_revision(fs, youngest_rev, subpool));
3763   checksum_list[youngest_rev] = checksum;
3764   svn_pool_clear(subpool);
3765 
3766   /* Alright, now we're just going to go crazy.  Let's make many more
3767      edits -- pseudo-random numbers and offsets of bytes changed to
3768      more pseudo-random values.  */
3769   for (j = youngest_rev; j < 30; j = youngest_rev)
3770     {
3771       SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
3772       SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3773       random_data_to_buffer(content_buffer, filesize, FALSE, seed);
3774       SVN_ERR(svn_checksum(&checksum, checksum_kind, contents.data,
3775                            contents.len, pool));
3776       SVN_ERR(svn_fs_apply_textdelta(&wh_func, &wh_baton, txn_root,
3777                                      "bigfile", NULL, NULL, subpool));
3778       SVN_ERR(svn_txdelta_send_string
3779               (&contents, wh_func, wh_baton, subpool));
3780       SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
3781       SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
3782       SVN_ERR(svn_fs_deltify_revision(fs, youngest_rev, subpool));
3783       checksum_list[youngest_rev] = checksum;
3784       svn_pool_clear(subpool);
3785     }
3786 
3787   /* Now, calculate an MD5 digest for the contents of our big ugly
3788      file in each revision currently in existence, and make the sure
3789      the checksum matches the checksum of the data prior to its
3790      commit. */
3791   for (j = youngest_rev; j > 0; j--)
3792     {
3793       SVN_ERR(svn_fs_revision_root(&rev_root, fs, j, subpool));
3794       SVN_ERR(get_file_checksum(&checksum, checksum_kind, rev_root, "bigfile",
3795                                 subpool));
3796       if (!svn_checksum_match(checksum, checksum_list[j]))
3797         return svn_error_createf
3798           (SVN_ERR_FS_GENERAL, NULL,
3799            "verify-checksum: checksum mismatch, revision %ld:\n"
3800            "   expected:  %s\n"
3801            "     actual:  %s\n", j,
3802         svn_checksum_to_cstring(checksum_list[j], pool),
3803         svn_checksum_to_cstring(checksum, pool));
3804 
3805       svn_pool_clear(subpool);
3806     }
3807 
3808   svn_pool_destroy(subpool);
3809   return SVN_NO_ERROR;
3810 }
3811 
3812 
3813 static svn_error_t *
small_file_integrity(const svn_test_opts_t * opts,apr_pool_t * pool)3814 small_file_integrity(const svn_test_opts_t *opts,
3815                      apr_pool_t *pool)
3816 {
3817   apr_uint32_t seed = (apr_uint32_t) apr_time_now();
3818 
3819   /* Just use a really small file size... */
3820   return file_integrity_helper(20, &seed, opts,
3821                                "test-repo-small-file-integrity", pool);
3822 }
3823 
3824 
3825 static svn_error_t *
almostmedium_file_integrity(const svn_test_opts_t * opts,apr_pool_t * pool)3826 almostmedium_file_integrity(const svn_test_opts_t *opts,
3827                             apr_pool_t *pool)
3828 {
3829   apr_uint32_t seed = (apr_uint32_t) apr_time_now();
3830 
3831   return file_integrity_helper(SVN_DELTA_WINDOW_SIZE - 1, &seed, opts,
3832                                "test-repo-almostmedium-file-integrity", pool);
3833 }
3834 
3835 
3836 static svn_error_t *
medium_file_integrity(const svn_test_opts_t * opts,apr_pool_t * pool)3837 medium_file_integrity(const svn_test_opts_t *opts,
3838                       apr_pool_t *pool)
3839 {
3840   apr_uint32_t seed = (apr_uint32_t) apr_time_now();
3841 
3842   /* Being no larger than the standard delta window size affects
3843      deltification internally, so test that. */
3844   return file_integrity_helper(SVN_DELTA_WINDOW_SIZE, &seed, opts,
3845                                "test-repo-medium-file-integrity", pool);
3846 }
3847 
3848 
3849 static svn_error_t *
large_file_integrity(const svn_test_opts_t * opts,apr_pool_t * pool)3850 large_file_integrity(const svn_test_opts_t *opts,
3851                      apr_pool_t *pool)
3852 {
3853   apr_uint32_t seed = (apr_uint32_t) apr_time_now();
3854 
3855   /* Being larger than the standard delta window size affects
3856      deltification internally, so test that. */
3857   return file_integrity_helper(SVN_DELTA_WINDOW_SIZE + 1, &seed, opts,
3858                                "test-repo-large-file-integrity", pool);
3859 }
3860 
3861 
3862 static svn_error_t *
check_root_revision(const svn_test_opts_t * opts,apr_pool_t * pool)3863 check_root_revision(const svn_test_opts_t *opts,
3864                     apr_pool_t *pool)
3865 {
3866   svn_fs_t *fs;
3867   svn_fs_txn_t *txn;
3868   svn_fs_root_t *txn_root, *rev_root;
3869   svn_revnum_t youngest_rev, test_rev;
3870   apr_pool_t *subpool = svn_pool_create(pool);
3871   int i;
3872 
3873   /* Create a filesystem and repository. */
3874   SVN_ERR(svn_test__create_fs(&fs, "test-repo-check-root-revision",
3875                               opts, pool));
3876 
3877   /* Create and commit the greek tree. */
3878   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
3879   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3880   SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
3881   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
3882   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
3883 
3884   /* Root node's revision should be the same as YOUNGEST_REV. */
3885   SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, subpool));
3886   SVN_ERR(svn_fs_node_created_rev(&test_rev, rev_root, "", subpool));
3887   if (test_rev != youngest_rev)
3888     return svn_error_createf
3889       (SVN_ERR_FS_GENERAL, NULL,
3890        "Root node in revision %ld has unexpected stored revision %ld",
3891        youngest_rev, test_rev);
3892   svn_pool_clear(subpool);
3893 
3894   for (i = 0; i < 10; i++)
3895     {
3896       /* Create and commit the greek tree. */
3897       SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
3898       SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
3899       SVN_ERR(svn_test__set_file_contents
3900               (txn_root, "iota",
3901                apr_psprintf(subpool, "iota version %d", i + 2), subpool));
3902 
3903       SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
3904       SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
3905 
3906       /* Root node's revision should be the same as YOUNGEST_REV. */
3907       SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, subpool));
3908       SVN_ERR(svn_fs_node_created_rev(&test_rev, rev_root, "", subpool));
3909       if (test_rev != youngest_rev)
3910         return svn_error_createf
3911           (SVN_ERR_FS_GENERAL, NULL,
3912            "Root node in revision %ld has unexpected stored revision %ld",
3913            youngest_rev, test_rev);
3914       svn_pool_clear(subpool);
3915     }
3916 
3917   svn_pool_destroy(subpool);
3918   return SVN_NO_ERROR;
3919 }
3920 
3921 
3922 struct node_created_rev_args {
3923   const char *path;
3924   svn_revnum_t rev;
3925 };
3926 
3927 
3928 static svn_error_t *
verify_path_revs(svn_fs_root_t * root,struct node_created_rev_args * args,int num_path_revs,apr_pool_t * pool)3929 verify_path_revs(svn_fs_root_t *root,
3930                  struct node_created_rev_args *args,
3931                  int num_path_revs,
3932                  apr_pool_t *pool)
3933 {
3934   apr_pool_t *subpool = svn_pool_create(pool);
3935   int i;
3936   svn_revnum_t rev;
3937 
3938   for (i = 0; i < num_path_revs; i++)
3939     {
3940       svn_pool_clear(subpool);
3941       SVN_ERR(svn_fs_node_created_rev(&rev, root, args[i].path, subpool));
3942       if (rev != args[i].rev)
3943         return svn_error_createf
3944           (SVN_ERR_FS_GENERAL, NULL,
3945            "verify_path_revs: '%s' has created rev '%ld' "
3946            "(expected '%ld')",
3947            args[i].path, rev, args[i].rev);
3948     }
3949 
3950   svn_pool_destroy(subpool);
3951   return SVN_NO_ERROR;
3952 }
3953 
3954 
3955 static svn_error_t *
test_node_created_rev(const svn_test_opts_t * opts,apr_pool_t * pool)3956 test_node_created_rev(const svn_test_opts_t *opts,
3957                       apr_pool_t *pool)
3958 {
3959   apr_pool_t *subpool = svn_pool_create(pool);
3960   svn_fs_t *fs;
3961   svn_fs_txn_t *txn;
3962   svn_fs_root_t *txn_root, *rev_root;
3963   svn_revnum_t youngest_rev = 0;
3964   int i;
3965   struct node_created_rev_args path_revs[21];
3966   const char *greek_paths[21] = {
3967     /*  0 */ "",
3968     /*  1 */ "iota",
3969     /*  2 */ "A",
3970     /*  3 */ "A/mu",
3971     /*  4 */ "A/B",
3972     /*  5 */ "A/B/lambda",
3973     /*  6 */ "A/B/E",
3974     /*  7 */ "A/B/E/alpha",
3975     /*  8 */ "A/B/E/beta",
3976     /*  9 */ "A/B/F",
3977     /* 10 */ "A/C",
3978     /* 11 */ "A/D",
3979     /* 12 */ "A/D/gamma",
3980     /* 13 */ "A/D/G",
3981     /* 14 */ "A/D/G/pi",
3982     /* 15 */ "A/D/G/rho",
3983     /* 16 */ "A/D/G/tau",
3984     /* 17 */ "A/D/H",
3985     /* 18 */ "A/D/H/chi",
3986     /* 19 */ "A/D/H/psi",
3987     /* 20 */ "A/D/H/omega",
3988   };
3989 
3990   /* Initialize the paths in our args list. */
3991   for (i = 0; i < 20; i++)
3992     path_revs[i].path = greek_paths[i];
3993 
3994   /* Create a filesystem and repository. */
3995   SVN_ERR(svn_test__create_fs(&fs, "test-repo-node-created-rev",
3996                               opts, pool));
3997 
3998   /* Created the greek tree in revision 1. */
3999   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
4000   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
4001   SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
4002 
4003   /* Now, prior to committing, all these nodes should have an invalid
4004      created rev.  After all, the rev has been created yet.  Verify
4005      this. */
4006   for (i = 0; i < 20; i++)
4007     path_revs[i].rev = SVN_INVALID_REVNUM;
4008   SVN_ERR(verify_path_revs(txn_root, path_revs, 20, subpool));
4009 
4010   /* Now commit the transaction. */
4011   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
4012   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4013 
4014   /* Now, we have a new revision, and all paths in it should have a
4015      created rev of 1.  Verify this. */
4016   SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, subpool));
4017   for (i = 0; i < 20; i++)
4018     path_revs[i].rev = 1;
4019   SVN_ERR(verify_path_revs(rev_root, path_revs, 20, subpool));
4020 
4021   /*** Let's make some changes/commits here and there, and make sure
4022        we can keep this whole created rev thing in good standing.  The
4023        general rule here is that prior to commit, mutable things have
4024        an invalid created rev, immutable things have their original
4025        created rev.  After the commit, those things which had invalid
4026        created revs in the transaction now have the youngest revision
4027        as their created rev.
4028 
4029        ### NOTE: Bubble-up currently affect the created revisions for
4030        directory nodes.  I'm not sure if this is the behavior we've
4031        settled on as desired.
4032   */
4033 
4034   /*** clear the per-commit pool */
4035   svn_pool_clear(subpool);
4036   /* begin a new transaction */
4037   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
4038   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
4039   /* The created revs on a txn root should be the same as on the rev
4040      root it came from, if we haven't made changes yet.  (See issue
4041      #2608.) */
4042   SVN_ERR(verify_path_revs(txn_root, path_revs, 20, subpool));
4043   /* make mods */
4044   SVN_ERR(svn_test__set_file_contents
4045           (txn_root, "iota", "pointless mod here", subpool));
4046   /* verify created revs */
4047   path_revs[0].rev = SVN_INVALID_REVNUM; /* (root) */
4048   path_revs[1].rev = SVN_INVALID_REVNUM; /* iota */
4049   SVN_ERR(verify_path_revs(txn_root, path_revs, 20, subpool));
4050   /* commit transaction */
4051   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
4052   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4053   /* get a revision root for the new revision */
4054   SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, subpool));
4055   /* verify created revs */
4056   path_revs[0].rev = 2; /* (root) */
4057   path_revs[1].rev = 2; /* iota */
4058   SVN_ERR(verify_path_revs(rev_root, path_revs, 20, subpool));
4059 
4060   /*** clear the per-commit pool */
4061   svn_pool_clear(subpool);
4062   /* begin a new transaction */
4063   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
4064   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
4065   /* make mods */
4066   SVN_ERR(svn_test__set_file_contents
4067           (txn_root, "A/D/H/omega", "pointless mod here", subpool));
4068   /* verify created revs */
4069   path_revs[0].rev  = SVN_INVALID_REVNUM; /* (root) */
4070   path_revs[2].rev  = SVN_INVALID_REVNUM; /* A */
4071   path_revs[11].rev = SVN_INVALID_REVNUM; /* D */
4072   path_revs[17].rev = SVN_INVALID_REVNUM; /* H */
4073   path_revs[20].rev = SVN_INVALID_REVNUM; /* omega */
4074   SVN_ERR(verify_path_revs(txn_root, path_revs, 20, subpool));
4075   /* commit transaction */
4076   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
4077   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4078   /* get a revision root for the new revision */
4079   SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, subpool));
4080   /* verify created revs */
4081   path_revs[0].rev  = 3; /* (root) */
4082   path_revs[2].rev  = 3; /* A */
4083   path_revs[11].rev = 3; /* D */
4084   path_revs[17].rev = 3; /* H */
4085   path_revs[20].rev = 3; /* omega */
4086   SVN_ERR(verify_path_revs(rev_root, path_revs, 20, subpool));
4087 
4088   /* Destroy the per-commit subpool. */
4089   svn_pool_destroy(subpool);
4090 
4091   return SVN_NO_ERROR;
4092 }
4093 
4094 
4095 static svn_error_t *
check_related(const svn_test_opts_t * opts,apr_pool_t * pool)4096 check_related(const svn_test_opts_t *opts,
4097               apr_pool_t *pool)
4098 {
4099   apr_pool_t *subpool = svn_pool_create(pool);
4100   svn_fs_t *fs;
4101   svn_fs_txn_t *txn;
4102   svn_fs_root_t *txn_root, *rev_root;
4103   svn_revnum_t youngest_rev = 0;
4104 
4105   /* Create a filesystem and repository. */
4106   SVN_ERR(svn_test__create_fs(&fs, "test-repo-check-related",
4107                               opts, pool));
4108 
4109   /*** Step I: Build up some state in our repository through a series
4110        of commits */
4111 
4112   /* Using files because bubble-up complicates the testing.  However,
4113      the algorithm itself is ambivalent about what type of node is
4114      being examined.
4115 
4116      - New files show up in this order (through time): A,B,C,D,E,F
4117      - Number following filename is the revision.
4118      - Vertical motion shows revision history
4119      - Horizontal motion show copy history.
4120 
4121      A1---------C4         E7
4122      |          |          |
4123      A2         C5         E8---F9
4124      |          |               |
4125      A3---B4    C6              F10
4126      |    |
4127      A4   B5----------D6
4128           |           |
4129           B6          D7
4130   */
4131   /* Revision 1 */
4132   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
4133   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
4134   SVN_ERR(svn_fs_make_file(txn_root, "A", subpool));
4135   SVN_ERR(svn_test__set_file_contents(txn_root, "A", "1", subpool));
4136   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
4137   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4138   svn_pool_clear(subpool);
4139   /* Revision 2 */
4140   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
4141   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
4142   SVN_ERR(svn_test__set_file_contents(txn_root, "A", "2", subpool));
4143   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
4144   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4145   svn_pool_clear(subpool);
4146   /* Revision 3 */
4147   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
4148   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
4149   SVN_ERR(svn_test__set_file_contents(txn_root, "A", "3", subpool));
4150   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
4151   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4152   svn_pool_clear(subpool);
4153   /* Revision 4 */
4154   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
4155   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
4156   SVN_ERR(svn_test__set_file_contents(txn_root, "A", "4", subpool));
4157   SVN_ERR(svn_fs_revision_root(&rev_root, fs, 3, subpool));
4158   SVN_ERR(svn_fs_copy(rev_root, "A", txn_root, "B", subpool));
4159   SVN_ERR(svn_test__set_file_contents(txn_root, "B", "4", subpool));
4160   SVN_ERR(svn_fs_revision_root(&rev_root, fs, 1, subpool));
4161   SVN_ERR(svn_fs_copy(rev_root, "A", txn_root, "C", subpool));
4162   SVN_ERR(svn_test__set_file_contents(txn_root, "C", "4", subpool));
4163   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
4164   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4165   svn_pool_clear(subpool);
4166   /* Revision 5 */
4167   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
4168   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
4169   SVN_ERR(svn_test__set_file_contents(txn_root, "B", "5", subpool));
4170   SVN_ERR(svn_test__set_file_contents(txn_root, "C", "5", subpool));
4171   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
4172   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4173   svn_pool_clear(subpool);
4174   /* Revision 6 */
4175   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
4176   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
4177   SVN_ERR(svn_test__set_file_contents(txn_root, "B", "6", subpool));
4178   SVN_ERR(svn_test__set_file_contents(txn_root, "C", "6", subpool));
4179   SVN_ERR(svn_fs_revision_root(&rev_root, fs, 5, subpool));
4180   SVN_ERR(svn_fs_copy(rev_root, "B", txn_root, "D", subpool));
4181   SVN_ERR(svn_test__set_file_contents(txn_root, "D", "5", subpool));
4182   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
4183   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4184   svn_pool_clear(subpool);
4185   /* Revision 7 */
4186   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
4187   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
4188   SVN_ERR(svn_test__set_file_contents(txn_root, "D", "7", subpool));
4189   SVN_ERR(svn_fs_make_file(txn_root, "E", subpool));
4190   SVN_ERR(svn_test__set_file_contents(txn_root, "E", "7", subpool));
4191   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
4192   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4193   svn_pool_clear(subpool);
4194   /* Revision 8 */
4195   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
4196   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
4197   SVN_ERR(svn_test__set_file_contents(txn_root, "E", "8", subpool));
4198   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
4199   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4200   svn_pool_clear(subpool);
4201   /* Revision 9 */
4202   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
4203   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
4204   SVN_ERR(svn_fs_revision_root(&rev_root, fs, 8, subpool));
4205   SVN_ERR(svn_fs_copy(rev_root, "E", txn_root, "F", subpool));
4206   SVN_ERR(svn_test__set_file_contents(txn_root, "F", "9", subpool));
4207   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
4208   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4209   svn_pool_clear(subpool);
4210   /* Revision 10 */
4211   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
4212   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
4213   SVN_ERR(svn_test__set_file_contents(txn_root, "F", "10", subpool));
4214   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
4215   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4216   svn_pool_clear(subpool);
4217 
4218   /*** Step II: Exhaustively verify relationship between all nodes in
4219        existence. */
4220   {
4221     int i, j;
4222 
4223     struct path_rev_t
4224     {
4225       const char *path;
4226       svn_revnum_t rev;
4227     };
4228 
4229     /* Our 16 existing files/revisions. */
4230     struct path_rev_t path_revs[16] = {
4231       { "A", 1 }, { "A", 2 }, { "A", 3 }, { "A", 4 },
4232       { "B", 4 }, { "B", 5 }, { "B", 6 }, { "C", 4 },
4233       { "C", 5 }, { "C", 6 }, { "D", 6 }, { "D", 7 },
4234       { "E", 7 }, { "E", 8 }, { "F", 9 }, { "F", 10 }
4235     };
4236 
4237     /* Latest revision that touched the respective path. */
4238     struct path_rev_t latest_changes[6] = {
4239       { "A", 4 }, { "B", 6 }, { "C", 6 },
4240       { "D", 7 }, { "E", 8 }, { "F", 10 }
4241     };
4242 
4243     int related_matrix[16][16] = {
4244       /* A1 ... F10 across the top here*/
4245       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* A1 */
4246       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* A2 */
4247       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* A3 */
4248       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* A4 */
4249       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* B4 */
4250       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* B5 */
4251       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* B6 */
4252       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* C4 */
4253       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* C5 */
4254       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* C6 */
4255       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* D6 */
4256       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* D7 */
4257       { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, /* E7 */
4258       { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, /* E8 */
4259       { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, /* F9 */
4260       { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }  /* F10 */
4261     };
4262 
4263     /* Here's the fun part.  Running the tests. */
4264     for (i = 0; i < 16; i++)
4265       {
4266         for (j = 0; j < 16; j++)
4267           {
4268             struct path_rev_t pr1 = path_revs[i];
4269             struct path_rev_t pr2 = path_revs[j];
4270             const svn_fs_id_t *id1, *id2;
4271             int related = 0;
4272             svn_fs_node_relation_t relation;
4273             svn_fs_root_t *rev_root1, *rev_root2;
4274 
4275             /* Get the ID for the first path/revision combination. */
4276             SVN_ERR(svn_fs_revision_root(&rev_root1, fs, pr1.rev, subpool));
4277             SVN_ERR(svn_fs_node_id(&id1, rev_root1, pr1.path, subpool));
4278 
4279             /* Get the ID for the second path/revision combination. */
4280             SVN_ERR(svn_fs_revision_root(&rev_root2, fs, pr2.rev, subpool));
4281             SVN_ERR(svn_fs_node_id(&id2, rev_root2, pr2.path, subpool));
4282 
4283             /* <exciting> Now, run the relationship check! </exciting> */
4284             related = svn_fs_check_related(id1, id2) ? 1 : 0;
4285             if (related == related_matrix[i][j])
4286               {
4287                 /* xlnt! */
4288               }
4289             else if (related && (! related_matrix[i][j]))
4290               {
4291                 return svn_error_createf
4292                   (SVN_ERR_TEST_FAILED, NULL,
4293                    "expected '%s:%d' to be related to '%s:%d'; it was not",
4294                    pr1.path, (int)pr1.rev, pr2.path, (int)pr2.rev);
4295               }
4296             else if ((! related) && related_matrix[i][j])
4297               {
4298                 return svn_error_createf
4299                   (SVN_ERR_TEST_FAILED, NULL,
4300                    "expected '%s:%d' to not be related to '%s:%d'; it was",
4301                    pr1.path, (int)pr1.rev, pr2.path, (int)pr2.rev);
4302               }
4303 
4304             /* Asking directly, i.e. without involving the noderev IDs as
4305              * an intermediate, should yield the same results. */
4306             SVN_ERR(svn_fs_node_relation(&relation, rev_root1, pr1.path,
4307                                          rev_root2, pr2.path, subpool));
4308             if (i == j)
4309               {
4310                 /* Identical note. */
4311                 if (!related || relation != svn_fs_node_unchanged)
4312                   {
4313                     return svn_error_createf
4314                       (SVN_ERR_TEST_FAILED, NULL,
4315                       "expected '%s:%d' to be the same as '%s:%d';"
4316                       " it was not",
4317                       pr1.path, (int)pr1.rev, pr2.path, (int)pr2.rev);
4318                   }
4319               }
4320             else if (related && relation != svn_fs_node_common_ancestor)
4321               {
4322                 return svn_error_createf
4323                   (SVN_ERR_TEST_FAILED, NULL,
4324                    "expected '%s:%d' to have a common ancestor with '%s:%d';"
4325                    " it had not",
4326                    pr1.path, (int)pr1.rev, pr2.path, (int)pr2.rev);
4327               }
4328             else if (!related && relation != svn_fs_node_unrelated)
4329               {
4330                 return svn_error_createf
4331                   (SVN_ERR_TEST_FAILED, NULL,
4332                    "expected '%s:%d' to not be related to '%s:%d'; it was",
4333                    pr1.path, (int)pr1.rev, pr2.path, (int)pr2.rev);
4334               }
4335 
4336             svn_pool_clear(subpool);
4337           } /* for ... */
4338       } /* for ... */
4339 
4340     /* Verify that the noderevs stay the same after their last change. */
4341     for (i = 0; i < 6; ++i)
4342       {
4343         const char *path = latest_changes[i].path;
4344         svn_revnum_t latest = latest_changes[i].rev;
4345         svn_fs_root_t *latest_root;
4346         svn_revnum_t rev;
4347         svn_fs_node_relation_t relation;
4348 
4349         /* FS root of the latest change. */
4350         svn_pool_clear(subpool);
4351         SVN_ERR(svn_fs_revision_root(&latest_root, fs, latest, subpool));
4352 
4353         /* All future revisions. */
4354         for (rev = latest + 1; rev <= 10; ++rev)
4355           {
4356             /* Query their noderev relationship to the latest change. */
4357             SVN_ERR(svn_fs_revision_root(&rev_root, fs, rev, subpool));
4358             SVN_ERR(svn_fs_node_relation(&relation, latest_root, path,
4359                                          rev_root, path, subpool));
4360 
4361             /* They shall use the same noderevs */
4362             if (relation != svn_fs_node_unchanged)
4363               {
4364                 return svn_error_createf
4365                   (SVN_ERR_TEST_FAILED, NULL,
4366                   "expected '%s:%d' to be the same as '%s:%d';"
4367                   " it was not",
4368                   path, (int)latest, path, (int)rev);
4369               }
4370           } /* for ... */
4371       } /* for ... */
4372   }
4373 
4374   /* Destroy the subpool. */
4375   svn_pool_destroy(subpool);
4376 
4377   return SVN_NO_ERROR;
4378 }
4379 
4380 
4381 static svn_error_t *
check_txn_related(const svn_test_opts_t * opts,apr_pool_t * pool)4382 check_txn_related(const svn_test_opts_t *opts,
4383                   apr_pool_t *pool)
4384 {
4385   apr_pool_t *subpool = svn_pool_create(pool);
4386   svn_fs_t *fs;
4387   svn_fs_txn_t *txn[3];
4388   svn_fs_root_t *root[3];
4389   svn_revnum_t youngest_rev = 0;
4390 
4391   /* Create a filesystem and repository. */
4392   SVN_ERR(svn_test__create_fs(&fs, "test-repo-check-txn-related",
4393                               opts, pool));
4394 
4395   /*** Step I: Build up some state in our repository through a series
4396        of commits */
4397 
4398   /* This is the node graph we are testing.  It contains one revision (r1)
4399      and two transactions, T1 and T2 - yet uncommitted.
4400 
4401      A is a file that exists in r1 (A-0) and gets modified in both txns.
4402      C is a copy of A-0 made in both txns.
4403      B is a new node created in both txns
4404      D is a file that exists in r1 (D-0) and never gets modified.
4405      / is the root folder, touched in r0, r1 and both txns (root-0)
4406      R is a copy of the root-0 made in both txns.
4407 
4408      The edges in the graph connect related noderevs:
4409 
4410                  +--A-0--+                D-0           +-root-0-+
4411                  |       |                              |        |
4412            +-----+       +-----+                 +------+        +------+
4413            |     |       |     |                 |      |        |      |
4414      B-1   C-1   A-1     A-2   C-2   B-2         R-1    root-1   root-2 R-2
4415   */
4416   /* Revision 1 */
4417   SVN_ERR(svn_fs_begin_txn(&txn[0], fs, youngest_rev, subpool));
4418   SVN_ERR(svn_fs_txn_root(&root[0], txn[0], subpool));
4419   SVN_ERR(svn_fs_make_file(root[0], "A", subpool));
4420   SVN_ERR(svn_test__set_file_contents(root[0], "A", "1", subpool));
4421   SVN_ERR(svn_fs_make_file(root[0], "D", subpool));
4422   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn[0], subpool));
4423   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4424   svn_pool_clear(subpool);
4425   SVN_ERR(svn_fs_revision_root(&root[0], fs, youngest_rev, pool));
4426 
4427   /* Transaction 1 */
4428   SVN_ERR(svn_fs_begin_txn(&txn[1], fs, youngest_rev, pool));
4429   SVN_ERR(svn_fs_txn_root(&root[1], txn[1], pool));
4430   SVN_ERR(svn_test__set_file_contents(root[1], "A", "2", pool));
4431   SVN_ERR(svn_fs_copy(root[0], "A", root[1], "C", pool));
4432   SVN_ERR(svn_fs_copy(root[0], "", root[1], "R", pool));
4433   SVN_ERR(svn_fs_make_file(root[1], "B", pool));
4434 
4435   /* Transaction 2 */
4436   SVN_ERR(svn_fs_begin_txn(&txn[2], fs, youngest_rev, pool));
4437   SVN_ERR(svn_fs_txn_root(&root[2], txn[2], pool));
4438   SVN_ERR(svn_test__set_file_contents(root[2], "A", "2", pool));
4439   SVN_ERR(svn_fs_copy(root[0], "A", root[2], "C", pool));
4440   SVN_ERR(svn_fs_copy(root[0], "", root[2], "R", pool));
4441   SVN_ERR(svn_fs_make_file(root[2], "B", pool));
4442 
4443   /*** Step II: Exhaustively verify relationship between all nodes in
4444        existence. */
4445   {
4446     enum { NODE_COUNT = 13 };
4447     int i, j;
4448 
4449     struct path_rev_t
4450     {
4451       const char *path;
4452       int root;
4453     };
4454 
4455     /* Our 16 existing files/revisions. */
4456     struct path_rev_t path_revs[NODE_COUNT] = {
4457       { "A", 0 }, { "A", 1 }, { "A", 2 },
4458       { "B", 1 }, { "B", 2 },
4459       { "C", 1 }, { "C", 2 },
4460       { "D", 0 },
4461       { "/", 0 }, { "/", 1 }, { "/", 2 },
4462       { "R", 1 }, { "R", 2 }
4463     };
4464 
4465     int related_matrix[NODE_COUNT][NODE_COUNT] = {
4466       /* A-0 ... R-2 across the top here*/
4467       { 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, /* A-0 */
4468       { 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, /* A-1 */
4469       { 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, /* A-2 */
4470       { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* B-1 */
4471       { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, /* B-2 */
4472       { 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, /* C-1 */
4473       { 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, /* C-2 */
4474       { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, /* D-0 */
4475       { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 }, /* root-0 */
4476       { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 }, /* root-1 */
4477       { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 }, /* root-2 */
4478       { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 }, /* R-1 */
4479       { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 }, /* R-2 */
4480     };
4481 
4482     /* Here's the fun part.  Running the tests. */
4483     for (i = 0; i < NODE_COUNT; i++)
4484       {
4485         for (j = 0; j < NODE_COUNT; j++)
4486           {
4487             struct path_rev_t pr1 = path_revs[i];
4488             struct path_rev_t pr2 = path_revs[j];
4489             const svn_fs_id_t *id1, *id2;
4490             int related = 0;
4491             svn_fs_node_relation_t relation;
4492 
4493             svn_pool_clear(subpool);
4494 
4495             /* Get the ID for the first path/revision combination. */
4496             SVN_ERR(svn_fs_node_id(&id1, root[pr1.root], pr1.path, subpool));
4497 
4498             /* Get the ID for the second path/revision combination. */
4499             SVN_ERR(svn_fs_node_id(&id2, root[pr2.root], pr2.path, subpool));
4500 
4501             /* <exciting> Now, run the relationship check! </exciting> */
4502             related = svn_fs_check_related(id1, id2) ? 1 : 0;
4503             if (related == related_matrix[i][j])
4504               {
4505                 /* xlnt! */
4506               }
4507             else if ((! related) && related_matrix[i][j])
4508               {
4509                 return svn_error_createf
4510                   (SVN_ERR_TEST_FAILED, NULL,
4511                    "expected '%s-%d' to be related to '%s-%d'; it was not",
4512                    pr1.path, pr1.root, pr2.path, pr2.root);
4513               }
4514             else if (related && (! related_matrix[i][j]))
4515               {
4516                 return svn_error_createf
4517                   (SVN_ERR_TEST_FAILED, NULL,
4518                    "expected '%s-%d' to not be related to '%s-%d'; it was",
4519                    pr1.path, pr1.root, pr2.path, pr2.root);
4520               }
4521 
4522             /* Asking directly, i.e. without involving the noderev IDs as
4523              * an intermediate, should yield the same results. */
4524             SVN_ERR(svn_fs_node_relation(&relation, root[pr1.root], pr1.path,
4525                                          root[pr2.root], pr2.path, subpool));
4526             if (i == j)
4527               {
4528                 /* Identical noderev. */
4529                 if (!related || relation != svn_fs_node_unchanged)
4530                   {
4531                     return svn_error_createf
4532                       (SVN_ERR_TEST_FAILED, NULL,
4533                       "expected '%s-%d' to be the same as '%s-%d';"
4534                       " it was not",
4535                       pr1.path, pr1.root, pr2.path, pr2.root);
4536                   }
4537               }
4538             else if (related && relation != svn_fs_node_common_ancestor)
4539               {
4540                 return svn_error_createf
4541                   (SVN_ERR_TEST_FAILED, NULL,
4542                    "expected '%s-%d' to have a common ancestor with '%s-%d';"
4543                    " it had not",
4544                    pr1.path, pr1.root, pr2.path, pr2.root);
4545               }
4546             else if (!related && relation != svn_fs_node_unrelated)
4547               {
4548                 return svn_error_createf
4549                   (SVN_ERR_TEST_FAILED, NULL,
4550                    "expected '%s-%d' to not be related to '%s-%d'; it was",
4551                    pr1.path, pr1.root, pr2.path, pr2.root);
4552               }
4553           } /* for ... */
4554       } /* for ... */
4555 
4556     /* Verify that the noderevs stay the same after their last change.
4557        There is only D that is not changed. */
4558     for (i = 1; i <= 2; ++i)
4559       {
4560         svn_fs_node_relation_t relation;
4561         svn_pool_clear(subpool);
4562 
4563         /* Query their noderev relationship to the latest change. */
4564         SVN_ERR(svn_fs_node_relation(&relation, root[i], "D",
4565                                      root[0], "D", subpool));
4566 
4567         /* They shall use the same noderevs */
4568         if (relation != svn_fs_node_unchanged)
4569           {
4570             return svn_error_createf
4571               (SVN_ERR_TEST_FAILED, NULL,
4572               "expected 'D-%d' to be the same as 'D-0'; it was not", i);
4573           }
4574       } /* for ... */
4575   }
4576 
4577   /* Destroy the subpool. */
4578   svn_pool_destroy(subpool);
4579 
4580   return SVN_NO_ERROR;
4581 }
4582 
4583 
4584 static svn_error_t *
branch_test(const svn_test_opts_t * opts,apr_pool_t * pool)4585 branch_test(const svn_test_opts_t *opts,
4586             apr_pool_t *pool)
4587 {
4588   apr_pool_t *spool = svn_pool_create(pool);
4589   svn_fs_t *fs;
4590   svn_fs_txn_t *txn;
4591   svn_fs_root_t *txn_root, *rev_root;
4592   svn_revnum_t youngest_rev = 0;
4593 
4594   /* Create a filesystem and repository. */
4595   SVN_ERR(svn_test__create_fs(&fs, "test-repo-branch",
4596                               opts, pool));
4597 
4598   /*** Revision 1:  Create the greek tree in revision.  ***/
4599   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
4600   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
4601   SVN_ERR(svn_test__create_greek_tree(txn_root, spool));
4602   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
4603   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4604   svn_pool_clear(spool);
4605 
4606   /*** Revision 2:  Copy A/D/G/rho to A/D/G/rho2.  ***/
4607   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
4608   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
4609   SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, spool));
4610   SVN_ERR(svn_fs_copy(rev_root, "A/D/G/rho", txn_root, "A/D/G/rho2", spool));
4611   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
4612   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4613   svn_pool_clear(spool);
4614 
4615   /*** Revision 3:  Copy A/D/G to A/D/G2.  ***/
4616   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
4617   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
4618   SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, spool));
4619   SVN_ERR(svn_fs_copy(rev_root, "A/D/G", txn_root, "A/D/G2", spool));
4620   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
4621   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4622   svn_pool_clear(spool);
4623 
4624   /*** Revision 4:  Copy A/D to A/D2.  ***/
4625   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
4626   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
4627   SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, spool));
4628   SVN_ERR(svn_fs_copy(rev_root, "A/D", txn_root, "A/D2", spool));
4629   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
4630   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4631   svn_pool_clear(spool);
4632 
4633   /*** Revision 5:  Edit all the rho's! ***/
4634   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
4635   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
4636   SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, spool));
4637   SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/G/rho",
4638                                       "Edited text.", spool));
4639   SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/G/rho2",
4640                                       "Edited text.", spool));
4641   SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/G2/rho",
4642                                       "Edited text.", spool));
4643   SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/G2/rho2",
4644                                       "Edited text.", spool));
4645   SVN_ERR(svn_test__set_file_contents(txn_root, "A/D2/G/rho",
4646                                       "Edited text.", spool));
4647   SVN_ERR(svn_test__set_file_contents(txn_root, "A/D2/G/rho2",
4648                                       "Edited text.", spool));
4649   SVN_ERR(svn_test__set_file_contents(txn_root, "A/D2/G2/rho",
4650                                       "Edited text.", spool));
4651   SVN_ERR(svn_test__set_file_contents(txn_root, "A/D2/G2/rho2",
4652                                       "Edited text.", spool));
4653   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
4654   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
4655 
4656   svn_pool_destroy(spool);
4657 
4658   return SVN_NO_ERROR;
4659 }
4660 
4661 
4662 /* Verify that file FILENAME under ROOT has the same contents checksum
4663  * as CONTENTS when comparing the checksums of the given TYPE.
4664  * Use POOL for temporary allocations. */
4665 static svn_error_t *
verify_file_checksum(svn_stringbuf_t * contents,svn_fs_root_t * root,const char * filename,svn_checksum_kind_t type,apr_pool_t * pool)4666 verify_file_checksum(svn_stringbuf_t *contents,
4667                      svn_fs_root_t *root,
4668                      const char *filename,
4669                      svn_checksum_kind_t type,
4670                      apr_pool_t *pool)
4671 {
4672   svn_checksum_t *expected_checksum, *actual_checksum;
4673 
4674   /* Write a file, compare the repository's idea of its checksum
4675      against our idea of its checksum.  They should be the same. */
4676   SVN_ERR(svn_checksum(&expected_checksum, type, contents->data,
4677                        contents->len, pool));
4678   SVN_ERR(svn_fs_file_checksum(&actual_checksum, type, root, filename, TRUE,
4679                                pool));
4680   if (!svn_checksum_match(expected_checksum, actual_checksum))
4681     return svn_error_createf
4682       (SVN_ERR_FS_GENERAL, NULL,
4683        "verify-checksum: checksum mismatch:\n"
4684        "   expected:  %s\n"
4685        "     actual:  %s\n",
4686        svn_checksum_to_cstring(expected_checksum, pool),
4687        svn_checksum_to_cstring(actual_checksum, pool));
4688 
4689   return SVN_NO_ERROR;
4690 }
4691 
4692 static svn_error_t *
verify_checksum(const svn_test_opts_t * opts,apr_pool_t * pool)4693 verify_checksum(const svn_test_opts_t *opts,
4694                 apr_pool_t *pool)
4695 {
4696   svn_fs_t *fs;
4697   svn_fs_txn_t *txn;
4698   svn_fs_root_t *txn_root, *rev_root;
4699   svn_stringbuf_t *str;
4700   svn_revnum_t rev;
4701 
4702   /* Write a file, compare the repository's idea of its checksum
4703      against our idea of its checksum.  They should be the same. */
4704   str = svn_stringbuf_create("My text editor charges me rent.", pool);
4705 
4706   SVN_ERR(svn_test__create_fs(&fs, "test-repo-verify-checksum",
4707                               opts, pool));
4708   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
4709   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
4710   SVN_ERR(svn_fs_make_file(txn_root, "fact", pool));
4711   SVN_ERR(svn_test__set_file_contents(txn_root, "fact", str->data, pool));
4712 
4713   /* Do it for the txn. */
4714   SVN_ERR(verify_file_checksum(str, txn_root, "fact", svn_checksum_md5,
4715                                pool));
4716   SVN_ERR(verify_file_checksum(str, txn_root, "fact", svn_checksum_sha1,
4717                                pool));
4718 
4719   /* Do it again - this time for the revision. */
4720   SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool));
4721   SVN_ERR(svn_fs_revision_root(&rev_root, fs, rev, pool));
4722   SVN_ERR(verify_file_checksum(str, rev_root, "fact", svn_checksum_md5,
4723                                pool));
4724   SVN_ERR(verify_file_checksum(str, rev_root, "fact", svn_checksum_sha1,
4725                                pool));
4726 
4727   return SVN_NO_ERROR;
4728 }
4729 
4730 
4731 /* Helper for closest_copy_test().  Verify that CLOSEST_PATH and the
4732    revision associated with CLOSEST_ROOT match the EXPECTED_PATH and
4733    EXPECTED_REVISION, respectively. */
4734 static svn_error_t *
test_closest_copy_pair(svn_fs_root_t * closest_root,const char * closest_path,svn_revnum_t expected_revision,const char * expected_path)4735 test_closest_copy_pair(svn_fs_root_t *closest_root,
4736                        const char *closest_path,
4737                        svn_revnum_t expected_revision,
4738                        const char *expected_path)
4739 {
4740   svn_revnum_t closest_rev = SVN_INVALID_REVNUM;
4741 
4742   /* Callers must pass valid -- EXPECTED_PATH and EXPECTED_REVISION
4743      come as a both-or-nothing pair. */
4744   assert(((! expected_path) && (! SVN_IS_VALID_REVNUM(expected_revision)))
4745          || (expected_path && SVN_IS_VALID_REVNUM(expected_revision)));
4746 
4747   /* CLOSEST_PATH and CLOSEST_ROOT come as a both-or-nothing pair, too. */
4748   if (closest_path && (! closest_root))
4749     return svn_error_create(SVN_ERR_FS_GENERAL, NULL,
4750                             "got closest path but no closest root");
4751   if ((! closest_path) && closest_root)
4752     return svn_error_create(SVN_ERR_FS_GENERAL, NULL,
4753                             "got closest root but no closest path");
4754 
4755   /* Now that our pairs are known sane, we can compare them. */
4756   if (closest_path && (! expected_path))
4757     return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
4758                              "got closest path ('%s') when none expected",
4759                              closest_path);
4760   if ((! closest_path) && expected_path)
4761     return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
4762                              "got no closest path; expected '%s'",
4763                              expected_path);
4764   if (closest_path && (strcmp(closest_path, expected_path) != 0))
4765     return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
4766                              "got a different closest path than expected:\n"
4767                              "   expected:  %s\n"
4768                              "     actual:  %s",
4769                              expected_path, closest_path);
4770   if (closest_root)
4771     closest_rev = svn_fs_revision_root_revision(closest_root);
4772   if (closest_rev != expected_revision)
4773     return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
4774                              "got a different closest rev than expected:\n"
4775                              "   expected:  %ld\n"
4776                              "     actual:  %ld",
4777                              expected_revision, closest_rev);
4778 
4779   return SVN_NO_ERROR;
4780 }
4781 
4782 
4783 static svn_error_t *
closest_copy_test(const svn_test_opts_t * opts,apr_pool_t * pool)4784 closest_copy_test(const svn_test_opts_t *opts,
4785                   apr_pool_t *pool)
4786 {
4787   svn_fs_t *fs;
4788   svn_fs_txn_t *txn;
4789   svn_fs_root_t *txn_root, *rev_root, *croot;
4790   svn_revnum_t after_rev;
4791   const char *cpath;
4792   apr_pool_t *spool = svn_pool_create(pool);
4793 
4794   /* Prepare a filesystem. */
4795   SVN_ERR(svn_test__create_fs(&fs, "test-repo-closest-copy",
4796                               opts, pool));
4797 
4798   /* In first txn, create and commit the greek tree. */
4799   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, spool));
4800   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
4801   SVN_ERR(svn_test__create_greek_tree(txn_root, spool));
4802   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, spool));
4803   svn_pool_clear(spool);
4804   SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, spool));
4805 
4806   /* Copy A to Z, and commit. */
4807   SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, spool));
4808   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
4809   SVN_ERR(svn_fs_copy(rev_root, "A", txn_root, "Z", spool));
4810   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, spool));
4811   svn_pool_clear(spool);
4812   SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, spool));
4813 
4814   /* Anything under Z should have a closest copy pair of ("/Z", 2), so
4815      we'll pick some spots to test.  Stuff under A should have no
4816      relevant closest copy. */
4817   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "Z", spool));
4818   SVN_ERR(test_closest_copy_pair(croot, cpath, 2, "/Z"));
4819   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "Z/D/G", spool));
4820   SVN_ERR(test_closest_copy_pair(croot, cpath, 2, "/Z"));
4821   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "Z/mu", spool));
4822   SVN_ERR(test_closest_copy_pair(croot, cpath, 2, "/Z"));
4823   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "Z/B/E/beta", spool));
4824   SVN_ERR(test_closest_copy_pair(croot, cpath, 2, "/Z"));
4825   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "A", spool));
4826   SVN_ERR(test_closest_copy_pair(croot, cpath, SVN_INVALID_REVNUM, NULL));
4827   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "A/D/G", spool));
4828   SVN_ERR(test_closest_copy_pair(croot, cpath, SVN_INVALID_REVNUM, NULL));
4829   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "A/mu", spool));
4830   SVN_ERR(test_closest_copy_pair(croot, cpath, SVN_INVALID_REVNUM, NULL));
4831   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "A/B/E/beta", spool));
4832   SVN_ERR(test_closest_copy_pair(croot, cpath, SVN_INVALID_REVNUM, NULL));
4833 
4834   /* Okay, so let's do some more stuff.  We'll edit Z/mu, copy A to
4835      Z2, copy A/D/H to Z2/D/H2, and edit Z2/D/H2/chi.  We'll also make
4836      new Z/t and Z2/D/H2/t files. */
4837   SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, spool));
4838   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
4839   SVN_ERR(svn_test__set_file_contents(txn_root, "Z/mu",
4840                                       "Edited text.", spool));
4841   SVN_ERR(svn_fs_copy(rev_root, "A", txn_root, "Z2", spool));
4842   SVN_ERR(svn_fs_copy(rev_root, "A/D/H", txn_root, "Z2/D/H2", spool));
4843   SVN_ERR(svn_test__set_file_contents(txn_root, "Z2/D/H2/chi",
4844                                       "Edited text.", spool));
4845   SVN_ERR(svn_fs_make_file(txn_root, "Z/t", pool));
4846   SVN_ERR(svn_fs_make_file(txn_root, "Z2/D/H2/t", pool));
4847   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, spool));
4848   svn_pool_clear(spool);
4849   SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, spool));
4850 
4851   /* Okay, just for kicks, let's modify Z2/D/H2/t.  Shouldn't affect
4852      its closest-copy-ness, right?  */
4853   SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, spool));
4854   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
4855   SVN_ERR(svn_test__set_file_contents(txn_root, "Z2/D/H2/t",
4856                                       "Edited text.", spool));
4857   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, spool));
4858   svn_pool_clear(spool);
4859   SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, spool));
4860 
4861   /* Now, we expect Z2/D/H2 to have a closest copy of ("/Z2/D/H2", 3)
4862      because of the deepest path rule.  We expected Z2/D to have a
4863      closest copy of ("/Z2", 3).  Z/mu should still have a closest
4864      copy of ("/Z", 2).  As for the two new files (Z/t and Z2/D/H2/t),
4865      neither should have a closest copy. */
4866   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "A/mu", spool));
4867   SVN_ERR(test_closest_copy_pair(croot, cpath, SVN_INVALID_REVNUM, NULL));
4868   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "Z/mu", spool));
4869   SVN_ERR(test_closest_copy_pair(croot, cpath, 2, "/Z"));
4870   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "Z2/D/H2", spool));
4871   SVN_ERR(test_closest_copy_pair(croot, cpath, 3, "/Z2/D/H2"));
4872   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "Z2/D", spool));
4873   SVN_ERR(test_closest_copy_pair(croot, cpath, 3, "/Z2"));
4874   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "Z/t", spool));
4875   SVN_ERR(test_closest_copy_pair(croot, cpath, SVN_INVALID_REVNUM, NULL));
4876   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "Z2/D/H2/t", spool));
4877   SVN_ERR(test_closest_copy_pair(croot, cpath, SVN_INVALID_REVNUM, NULL));
4878 
4879   return SVN_NO_ERROR;
4880 }
4881 
4882 static svn_error_t *
root_revisions(const svn_test_opts_t * opts,apr_pool_t * pool)4883 root_revisions(const svn_test_opts_t *opts,
4884                apr_pool_t *pool)
4885 {
4886   svn_fs_t *fs;
4887   svn_fs_txn_t *txn;
4888   svn_fs_root_t *txn_root, *rev_root;
4889   svn_revnum_t after_rev, fetched_rev;
4890   apr_pool_t *spool = svn_pool_create(pool);
4891 
4892   /* Prepare a filesystem. */
4893   SVN_ERR(svn_test__create_fs(&fs, "test-repo-root-revisions",
4894                               opts, pool));
4895 
4896   /* In first txn, create and commit the greek tree. */
4897   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, spool));
4898   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
4899   SVN_ERR(svn_test__create_greek_tree(txn_root, spool));
4900   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, spool));
4901 
4902   /* First, verify that a revision root based on our new revision
4903      reports the correct associated revision. */
4904   SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, spool));
4905   fetched_rev = svn_fs_revision_root_revision(rev_root);
4906   if (after_rev != fetched_rev)
4907     return svn_error_createf
4908       (SVN_ERR_TEST_FAILED, NULL,
4909        "expected revision '%d'; "
4910        "got '%d' from svn_fs_revision_root_revision(rev_root)",
4911        (int)after_rev, (int)fetched_rev);
4912 
4913   /* Then verify that we can't ask about the txn-base-rev from a
4914      revision root. */
4915   fetched_rev = svn_fs_txn_root_base_revision(rev_root);
4916   if (fetched_rev != SVN_INVALID_REVNUM)
4917     return svn_error_createf
4918       (SVN_ERR_TEST_FAILED, NULL,
4919        "expected SVN_INVALID_REVNUM; "
4920        "got '%d' from svn_fs_txn_root_base_revision(rev_root)",
4921        (int)fetched_rev);
4922 
4923   /* Now, create a second txn based on AFTER_REV. */
4924   SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, spool));
4925   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
4926 
4927   /* Verify that it reports the right base revision. */
4928   fetched_rev = svn_fs_txn_root_base_revision(txn_root);
4929   if (after_rev != fetched_rev)
4930     return svn_error_createf
4931       (SVN_ERR_TEST_FAILED, NULL,
4932        "expected '%d'; "
4933        "got '%d' from svn_fs_txn_root_base_revision(txn_root)",
4934        (int)after_rev, (int)fetched_rev);
4935 
4936   /* Then verify that we can't ask about the rev-root-rev from a
4937      txn root. */
4938   fetched_rev = svn_fs_revision_root_revision(txn_root);
4939   if (fetched_rev != SVN_INVALID_REVNUM)
4940     return svn_error_createf
4941       (SVN_ERR_TEST_FAILED, NULL,
4942        "expected SVN_INVALID_REVNUM; "
4943        "got '%d' from svn_fs_revision_root_revision(txn_root)",
4944        (int)fetched_rev);
4945 
4946   return SVN_NO_ERROR;
4947 }
4948 
4949 
4950 static svn_error_t *
unordered_txn_dirprops(const svn_test_opts_t * opts,apr_pool_t * pool)4951 unordered_txn_dirprops(const svn_test_opts_t *opts,
4952                        apr_pool_t *pool)
4953 {
4954   svn_fs_t *fs;
4955   svn_fs_txn_t *txn, *txn2;
4956   svn_fs_root_t *txn_root, *txn_root2;
4957   svn_string_t pval;
4958   svn_revnum_t new_rev, not_rev;
4959   svn_boolean_t is_bdb = strcmp(opts->fs_type, SVN_FS_TYPE_BDB) == 0;
4960 
4961   /* This is a regression test for issue #2751. */
4962 
4963   /* Prepare a filesystem. */
4964   SVN_ERR(svn_test__create_fs(&fs, "test-repo-unordered-txn-dirprops",
4965                               opts, pool));
4966 
4967   /* Create and commit the greek tree. */
4968   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
4969   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
4970   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
4971   SVN_ERR(test_commit_txn(&new_rev, txn, NULL, pool));
4972 
4973   /* Open two transactions */
4974   SVN_ERR(svn_fs_begin_txn(&txn, fs, new_rev, pool));
4975   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
4976   SVN_ERR(svn_fs_begin_txn(&txn2, fs, new_rev, pool));
4977   SVN_ERR(svn_fs_txn_root(&txn_root2, txn2, pool));
4978 
4979   /* Change a child file in one. */
4980   SVN_ERR(svn_test__set_file_contents(txn_root, "/A/B/E/alpha",
4981                                       "New contents", pool));
4982 
4983   /* Change dir props in the other.  (We're using svn:mergeinfo
4984      property just to make sure special handling logic for that
4985      property doesn't croak.) */
4986   SET_STR(&pval, "/A/C:1");
4987   SVN_ERR(svn_fs_change_node_prop(txn_root2, "/A/B", "svn:mergeinfo",
4988                                   &pval, pool));
4989 
4990   /* Commit the second one first. */
4991   SVN_ERR(test_commit_txn(&new_rev, txn2, NULL, pool));
4992 
4993   /* Then commit the first -- but expect a conflict due to the
4994      propchanges made by the other txn. */
4995   SVN_ERR(test_commit_txn(&not_rev, txn, "/A/B", pool));
4996   SVN_ERR(svn_fs_abort_txn(txn, pool));
4997 
4998   /* Now, let's try those in reverse.  Open two transactions */
4999   SVN_ERR(svn_fs_begin_txn(&txn, fs, new_rev, pool));
5000   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
5001   SVN_ERR(svn_fs_begin_txn(&txn2, fs, new_rev, pool));
5002   SVN_ERR(svn_fs_txn_root(&txn_root2, txn2, pool));
5003 
5004   /* Change a child file in one. */
5005   SVN_ERR(svn_test__set_file_contents(txn_root, "/A/B/E/alpha",
5006                                       "New contents", pool));
5007 
5008   /* Change dir props in the other. */
5009   SET_STR(&pval, "/A/C:1");
5010   SVN_ERR(svn_fs_change_node_prop(txn_root2, "/A/B", "svn:mergeinfo",
5011                                   &pval, pool));
5012 
5013   /* Commit the first one first. */
5014   SVN_ERR(test_commit_txn(&new_rev, txn, NULL, pool));
5015 
5016   /* Some backends are cleverer than others. */
5017   if (is_bdb)
5018     {
5019       /* Then commit the second -- but expect an conflict because the
5020          directory wasn't up-to-date, which is required for propchanges. */
5021       SVN_ERR(test_commit_txn(&not_rev, txn2, "/A/B", pool));
5022       SVN_ERR(svn_fs_abort_txn(txn2, pool));
5023     }
5024   else
5025     {
5026       /* Then commit the second -- there will be no conflict despite the
5027          directory being out-of-data because the properties as well as the
5028          directory structure (list of nodes) was up-to-date. */
5029       SVN_ERR(test_commit_txn(&not_rev, txn2, NULL, pool));
5030     }
5031 
5032   return SVN_NO_ERROR;
5033 }
5034 
5035 static svn_error_t *
set_uuid(const svn_test_opts_t * opts,apr_pool_t * pool)5036 set_uuid(const svn_test_opts_t *opts,
5037          apr_pool_t *pool)
5038 {
5039   svn_fs_t *fs;
5040   const char *fixed_uuid = svn_uuid_generate(pool);
5041   const char *fetched_uuid;
5042 
5043   /* Prepare a filesystem. */
5044   SVN_ERR(svn_test__create_fs(&fs, "test-repo-set-uuid",
5045                               opts, pool));
5046 
5047   /* Set the repository UUID to something fixed. */
5048   SVN_ERR(svn_fs_set_uuid(fs, fixed_uuid, pool));
5049 
5050   /* Make sure we get back what we set. */
5051   SVN_ERR(svn_fs_get_uuid(fs, &fetched_uuid, pool));
5052   if (strcmp(fixed_uuid, fetched_uuid) != 0)
5053     return svn_error_createf
5054       (SVN_ERR_TEST_FAILED, NULL, "expected UUID '%s'; got '%s'",
5055        fixed_uuid, fetched_uuid);
5056 
5057   /* Set the repository UUID to something new (and unknown). */
5058   SVN_ERR(svn_fs_set_uuid(fs, NULL, pool));
5059 
5060   /* Make sure we *don't* get back what we previously set (after all,
5061      this stuff is supposed to be universally unique!). */
5062   SVN_ERR(svn_fs_get_uuid(fs, &fetched_uuid, pool));
5063   if (strcmp(fixed_uuid, fetched_uuid) == 0)
5064     return svn_error_createf
5065       (SVN_ERR_TEST_FAILED, NULL,
5066        "expected something other than UUID '%s', but got that one",
5067        fixed_uuid);
5068 
5069   return SVN_NO_ERROR;
5070 }
5071 
5072 static svn_error_t *
node_origin_rev(const svn_test_opts_t * opts,apr_pool_t * pool)5073 node_origin_rev(const svn_test_opts_t *opts,
5074                 apr_pool_t *pool)
5075 {
5076   apr_pool_t *subpool = svn_pool_create(pool);
5077   svn_fs_t *fs;
5078   svn_fs_txn_t *txn;
5079   svn_fs_root_t *txn_root, *root;
5080   svn_revnum_t youngest_rev = 0;
5081   int i;
5082 
5083   struct path_rev_t {
5084     const char *path;
5085     svn_revnum_t rev;
5086   };
5087 
5088   /* Create the repository. */
5089   SVN_ERR(svn_test__create_fs(&fs, "test-repo-node-origin-rev",
5090                               opts, pool));
5091 
5092   /* Revision 1: Create the Greek tree.  */
5093   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
5094   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
5095   SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
5096   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
5097   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
5098   svn_pool_clear(subpool);
5099 
5100   /* Revision 2: Modify A/D/H/chi and A/B/E/alpha.  */
5101   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
5102   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
5103   SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/chi", "2", subpool));
5104   SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/alpha", "2", subpool));
5105   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
5106   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
5107   svn_pool_clear(subpool);
5108 
5109   /* Revision 3: Copy A/D to A/D2, and create A/D2/floop new.  */
5110   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
5111   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
5112   SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, subpool));
5113   SVN_ERR(svn_fs_copy(root, "A/D", txn_root, "A/D2", subpool));
5114   SVN_ERR(svn_fs_make_file(txn_root, "A/D2/floop", subpool));
5115   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
5116   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
5117   svn_pool_clear(subpool);
5118 
5119   /* Revision 4: Modify A/D/H/chi and A/D2/H/chi.  */
5120   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
5121   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
5122   SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/chi", "4", subpool));
5123   SVN_ERR(svn_test__set_file_contents(txn_root, "A/D2/H/chi", "4", subpool));
5124   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
5125   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
5126   svn_pool_clear(subpool);
5127 
5128   /* Revision 5: Delete A/D2/G, add A/B/E/alfalfa.  */
5129   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
5130   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
5131   SVN_ERR(svn_fs_delete(txn_root, "A/D2/G", subpool));
5132   SVN_ERR(svn_fs_make_file(txn_root, "A/B/E/alfalfa", subpool));
5133   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
5134   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
5135   svn_pool_clear(subpool);
5136 
5137   /* Revision 6: Restore A/D2/G (from version 4).  */
5138   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
5139   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
5140   SVN_ERR(svn_fs_revision_root(&root, fs, 4, subpool));
5141   SVN_ERR(svn_fs_copy(root, "A/D2/G", txn_root, "A/D2/G", subpool));
5142   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
5143   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
5144   svn_pool_clear(subpool);
5145 
5146   /* Revision 7: Move A/D2 to A/D (replacing it), Add a new file A/D2,
5147      and tweak A/D/floop.  */
5148   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
5149   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
5150   SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, subpool));
5151   SVN_ERR(svn_fs_delete(txn_root, "A/D", subpool));
5152   SVN_ERR(svn_fs_copy(root, "A/D2", txn_root, "A/D", subpool));
5153   SVN_ERR(svn_fs_delete(txn_root, "A/D2", subpool));
5154   SVN_ERR(svn_fs_make_file(txn_root, "A/D2", subpool));
5155   SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/floop", "7", subpool));
5156   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
5157   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
5158   svn_pool_clear(subpool);
5159 
5160   /* Now test some origin revisions. */
5161   {
5162     struct path_rev_t pathrevs[5] = { { "A/D",             1 },
5163                                       { "A/D/floop",       3 },
5164                                       { "A/D2",            7 },
5165                                       { "iota",            1 },
5166                                       { "A/B/E/alfalfa",   5 } };
5167 
5168     SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, pool));
5169     for (i = 0; i < (sizeof(pathrevs) / sizeof(struct path_rev_t)); i++)
5170       {
5171         struct path_rev_t path_rev = pathrevs[i];
5172         svn_revnum_t revision;
5173         SVN_ERR(svn_fs_node_origin_rev(&revision, root, path_rev.path, pool));
5174         if (path_rev.rev != revision)
5175           return svn_error_createf
5176             (SVN_ERR_TEST_FAILED, NULL,
5177              "expected origin revision of '%ld' for '%s'; got '%ld'",
5178              path_rev.rev, path_rev.path, revision);
5179       }
5180   }
5181 
5182   /* Also, we'll check a couple of queries into a transaction root. */
5183   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
5184   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
5185   SVN_ERR(svn_fs_make_file(txn_root, "bloop", subpool));
5186   SVN_ERR(svn_fs_make_dir(txn_root, "A/D/blarp", subpool));
5187 
5188   {
5189     struct path_rev_t pathrevs[6] = { { "A/D",             1 },
5190                                       { "A/D/floop",       3 },
5191                                       { "bloop",          -1 },
5192                                       { "A/D/blarp",      -1 },
5193                                       { "iota",            1 },
5194                                       { "A/B/E/alfalfa",   5 } };
5195 
5196     root = txn_root;
5197     for (i = 0; i < (sizeof(pathrevs) / sizeof(struct path_rev_t)); i++)
5198       {
5199         struct path_rev_t path_rev = pathrevs[i];
5200         svn_revnum_t revision;
5201         SVN_ERR(svn_fs_node_origin_rev(&revision, root, path_rev.path, pool));
5202         if (! SVN_IS_VALID_REVNUM(revision))
5203           revision = -1;
5204         if (path_rev.rev != revision)
5205           return svn_error_createf
5206             (SVN_ERR_TEST_FAILED, NULL,
5207              "expected origin revision of '%ld' for '%s'; got '%ld'",
5208              path_rev.rev, path_rev.path, revision);
5209       }
5210   }
5211 
5212   return SVN_NO_ERROR;
5213 }
5214 
5215 
5216 /* Helper: call svn_fs_history_location() and check the results. */
5217 static svn_error_t *
check_history_location(const char * expected_path,svn_revnum_t expected_revision,svn_fs_history_t * history,apr_pool_t * pool)5218 check_history_location(const char *expected_path,
5219                        svn_revnum_t expected_revision,
5220                        svn_fs_history_t *history,
5221                        apr_pool_t *pool)
5222 {
5223   const char *actual_path;
5224   svn_revnum_t actual_revision;
5225 
5226   SVN_ERR(svn_fs_history_location(&actual_path, &actual_revision,
5227                                   history, pool));
5228 
5229   /* Validate the location against our expectations. */
5230   if (actual_revision != expected_revision
5231       || (actual_path && expected_path && strcmp(actual_path, expected_path))
5232       || (actual_path != NULL) != (expected_path != NULL))
5233     {
5234       return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
5235                                "svn_fs_history_location() failed:\n"
5236                                "  expected '%s@%ld'\n"
5237                                "     found '%s@%ld",
5238                                expected_path ? expected_path : "(null)",
5239                                expected_revision,
5240                                actual_path ? actual_path : "(null)",
5241                                actual_revision);
5242     }
5243 
5244   return SVN_NO_ERROR;
5245 }
5246 
5247 /* Test svn_fs_history_*(). */
5248 static svn_error_t *
node_history(const svn_test_opts_t * opts,apr_pool_t * pool)5249 node_history(const svn_test_opts_t *opts,
5250              apr_pool_t *pool)
5251 {
5252   svn_fs_t *fs;
5253   svn_fs_txn_t *txn;
5254   svn_fs_root_t *txn_root;
5255   svn_revnum_t after_rev;
5256 
5257   /* Prepare a txn to receive the greek tree. */
5258   SVN_ERR(svn_test__create_fs(&fs, "test-repo-node-history",
5259                               opts, pool));
5260   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
5261   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
5262 
5263   /* Create and verify the greek tree. */
5264   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
5265   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
5266 
5267   /* Make some changes, following copy_test() above. */
5268 
5269   /* r2: copy pi to pi2, with textmods. */
5270   {
5271     svn_fs_root_t *rev_root;
5272 
5273     SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, pool));
5274     SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, pool));
5275     SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
5276     SVN_ERR(svn_fs_copy(rev_root, "A/D/G/pi",
5277                         txn_root, "A/D/H/pi2",
5278                         pool));
5279     SVN_ERR(svn_test__set_file_contents
5280             (txn_root, "A/D/H/pi2", "This is the file 'pi2'.\n", pool));
5281     SVN_ERR(test_commit_txn(&after_rev, txn, NULL, pool));
5282   }
5283 
5284   /* Go back in history: pi2@r2 -> pi@r1 */
5285   {
5286     svn_fs_history_t *history;
5287     svn_fs_root_t *rev_root;
5288 
5289     SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, pool));
5290 
5291     /* Fetch a history object, and walk it until its start. */
5292 
5293     SVN_ERR(svn_fs_node_history(&history, rev_root, "A/D/H/pi2", pool));
5294     SVN_ERR(check_history_location("/A/D/H/pi2", 2, history, pool));
5295 
5296     SVN_ERR(svn_fs_history_prev(&history, history, TRUE, pool));
5297     SVN_ERR(check_history_location("/A/D/H/pi2", 2, history, pool));
5298 
5299     SVN_ERR(svn_fs_history_prev(&history, history, TRUE, pool));
5300     SVN_ERR(check_history_location("/A/D/G/pi", 1, history, pool));
5301 
5302     SVN_ERR(svn_fs_history_prev(&history, history, TRUE, pool));
5303     SVN_TEST_ASSERT(history == NULL);
5304   }
5305 
5306   return SVN_NO_ERROR;
5307 }
5308 
5309 /* Test svn_fs_delete_fs(). */
5310 static svn_error_t *
delete_fs(const svn_test_opts_t * opts,apr_pool_t * pool)5311 delete_fs(const svn_test_opts_t *opts,
5312              apr_pool_t *pool)
5313 {
5314   const char *path;
5315   svn_node_kind_t kind;
5316 
5317   /* We have to use a subpool to close the svn_fs_t before calling
5318      svn_fs_delete_fs.  See issue 4264. */
5319   {
5320     svn_fs_t *fs;
5321     apr_pool_t *subpool = svn_pool_create(pool);
5322     SVN_ERR(svn_test__create_fs(&fs, "test-repo-delete-fs", opts, subpool));
5323     path = svn_fs_path(fs, pool);
5324     svn_pool_destroy(subpool);
5325   }
5326 
5327   SVN_ERR(svn_io_check_path(path, &kind, pool));
5328   SVN_TEST_ASSERT(kind != svn_node_none);
5329   SVN_ERR(svn_fs_delete_fs(path, pool));
5330   SVN_ERR(svn_io_check_path(path, &kind, pool));
5331   SVN_TEST_ASSERT(kind == svn_node_none);
5332 
5333   /* Recreate dir so that test cleanup doesn't fail. */
5334   SVN_ERR(svn_io_dir_make(path, APR_OS_DEFAULT, pool));
5335 
5336   return SVN_NO_ERROR;
5337 }
5338 
5339 /* Issue 4340, "filenames containing \n corrupt FSFS repositories" */
5340 static svn_error_t *
filename_trailing_newline(const svn_test_opts_t * opts,apr_pool_t * pool)5341 filename_trailing_newline(const svn_test_opts_t *opts,
5342                           apr_pool_t *pool)
5343 {
5344   apr_pool_t *subpool = svn_pool_create(pool);
5345   svn_fs_t *fs;
5346   svn_fs_txn_t *txn;
5347   svn_fs_root_t *txn_root, *root;
5348   svn_revnum_t youngest_rev = 0;
5349   svn_error_t *err;
5350 
5351   /* The FS API wants \n to be permitted, but FSFS never implemented that.
5352    * Moreover, formats like svn:mergeinfo and svn:externals don't support
5353    * it either.  So, we can't have newlines in file names in any FS.
5354    */
5355   SVN_ERR(svn_test__create_fs(&fs, "test-repo-filename-trailing-newline",
5356                               opts, pool));
5357 
5358   /* Revision 1:  Add a directory /foo  */
5359   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
5360   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
5361   SVN_ERR(svn_fs_make_dir(txn_root, "/foo", subpool));
5362   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
5363   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
5364   svn_pool_clear(subpool);
5365 
5366   /* Attempt to copy /foo to "/bar\n". This should fail. */
5367   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
5368   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
5369   SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, subpool));
5370   err = svn_fs_copy(root, "/foo", txn_root, "/bar\n", subpool);
5371   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_FS_PATH_SYNTAX);
5372 
5373   /* Attempt to create a file /foo/baz\n. This should fail. */
5374   err = svn_fs_make_file(txn_root, "/foo/baz\n", subpool);
5375   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_FS_PATH_SYNTAX);
5376 
5377   /* Attempt to create a directory /foo/bang\n. This should fail. */
5378   err = svn_fs_make_dir(txn_root, "/foo/bang\n", subpool);
5379   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_FS_PATH_SYNTAX);
5380 
5381   return SVN_NO_ERROR;
5382 }
5383 
5384 static svn_error_t *
test_fs_info_format(const svn_test_opts_t * opts,apr_pool_t * pool)5385 test_fs_info_format(const svn_test_opts_t *opts,
5386                     apr_pool_t *pool)
5387 {
5388   svn_fs_t *fs;
5389   int fs_format;
5390   svn_version_t *supports_version;
5391   svn_version_t v1_5_0 = {1, 5, 0, ""};
5392   svn_version_t v1_10_0 = {1, 10, 0, ""};
5393   svn_test_opts_t opts2;
5394   svn_boolean_t is_fsx = strcmp(opts->fs_type, "fsx") == 0;
5395 
5396   opts2 = *opts;
5397   opts2.server_minor_version = is_fsx ? 10 : 5;
5398 
5399   SVN_ERR(svn_test__create_fs(&fs, "test-repo-fs-format-info", &opts2, pool));
5400   SVN_ERR(svn_fs_info_format(&fs_format, &supports_version, fs, pool, pool));
5401 
5402   if (is_fsx)
5403     {
5404       SVN_TEST_ASSERT(fs_format == 2);
5405       SVN_TEST_ASSERT(svn_ver_equal(supports_version, &v1_10_0));
5406     }
5407   else
5408     {
5409        /* happens to be the same for FSFS and BDB */
5410       SVN_TEST_ASSERT(fs_format == 3);
5411       SVN_TEST_ASSERT(svn_ver_equal(supports_version, &v1_5_0));
5412     }
5413 
5414   return SVN_NO_ERROR;
5415 }
5416 
5417 /* Sleeps until apr_time_now() value changes. */
sleep_for_timestamps(void)5418 static void sleep_for_timestamps(void)
5419 {
5420   apr_time_t start = apr_time_now();
5421 
5422   while (start == apr_time_now())
5423     {
5424       apr_sleep(APR_USEC_PER_SEC / 1000);
5425     }
5426 }
5427 
5428 static svn_error_t *
commit_timestamp(const svn_test_opts_t * opts,apr_pool_t * pool)5429 commit_timestamp(const svn_test_opts_t *opts,
5430                  apr_pool_t *pool)
5431 {
5432   svn_fs_t *fs;
5433   svn_fs_txn_t *txn;
5434   svn_fs_root_t *txn_root;
5435   svn_string_t *date = svn_string_create("Yesterday", pool);
5436   svn_revnum_t rev = 0;
5437   apr_hash_t *proplist;
5438   svn_string_t *svn_date;
5439   svn_string_t *txn_svn_date;
5440 
5441   SVN_ERR(svn_test__create_fs(&fs, "test-repo-fs-commit-timestamp",
5442                               opts, pool));
5443 
5444   /* Commit with a specified svn:date. */
5445   SVN_ERR(svn_fs_begin_txn2(&txn, fs, rev, SVN_FS_TXN_CLIENT_DATE, pool));
5446   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
5447   SVN_ERR(svn_fs_make_dir(txn_root, "/foo", pool));
5448   SVN_ERR(svn_fs_change_txn_prop(txn, SVN_PROP_REVISION_DATE, date, pool));
5449   SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool));
5450 
5451   SVN_ERR(svn_fs_revision_proplist(&proplist, fs, rev, pool));
5452   svn_date = apr_hash_get(proplist, SVN_PROP_REVISION_DATE,
5453                           APR_HASH_KEY_STRING);
5454   SVN_TEST_ASSERT(svn_date && !strcmp(svn_date->data, date->data));
5455 
5456   /* Commit that overwrites the specified svn:date. */
5457   SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, pool));
5458   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
5459   SVN_ERR(svn_fs_make_dir(txn_root, "/bar", pool));
5460   SVN_ERR(svn_fs_change_txn_prop(txn, SVN_PROP_REVISION_DATE, date, pool));
5461   SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool));
5462 
5463   SVN_ERR(svn_fs_revision_proplist(&proplist, fs, rev, pool));
5464   svn_date = apr_hash_get(proplist, SVN_PROP_REVISION_DATE,
5465                           APR_HASH_KEY_STRING);
5466   SVN_TEST_ASSERT(svn_date && strcmp(svn_date->data, date->data));
5467 
5468   /* Commit with a missing svn:date. */
5469   SVN_ERR(svn_fs_begin_txn2(&txn, fs, rev, SVN_FS_TXN_CLIENT_DATE, pool));
5470   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
5471   SVN_ERR(svn_fs_make_dir(txn_root, "/zag", pool));
5472   SVN_ERR(svn_fs_change_txn_prop(txn, SVN_PROP_REVISION_DATE, NULL, pool));
5473   SVN_ERR(svn_fs_txn_prop(&svn_date, txn, SVN_PROP_REVISION_DATE, pool));
5474   SVN_TEST_ASSERT(!svn_date);
5475   SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool));
5476 
5477   SVN_ERR(svn_fs_revision_proplist(&proplist, fs, rev, pool));
5478   svn_date = apr_hash_get(proplist, SVN_PROP_REVISION_DATE,
5479                           APR_HASH_KEY_STRING);
5480   SVN_TEST_ASSERT(!svn_date);
5481 
5482   /* Commit that overwites a missing svn:date. */
5483   SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, pool));
5484   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
5485   SVN_ERR(svn_fs_make_dir(txn_root, "/zig", pool));
5486   SVN_ERR(svn_fs_change_txn_prop(txn, SVN_PROP_REVISION_DATE, NULL, pool));
5487   SVN_ERR(svn_fs_txn_prop(&svn_date, txn, SVN_PROP_REVISION_DATE, pool));
5488   SVN_TEST_ASSERT(!svn_date);
5489   SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool));
5490 
5491   SVN_ERR(svn_fs_revision_proplist(&proplist, fs, rev, pool));
5492   svn_date = apr_hash_get(proplist, SVN_PROP_REVISION_DATE,
5493                           APR_HASH_KEY_STRING);
5494   SVN_TEST_ASSERT(svn_date);
5495 
5496   /* Commit that doesn't do anything special about svn:date. */
5497   SVN_ERR(svn_fs_begin_txn2(&txn, fs, rev, 0, pool));
5498   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
5499   SVN_ERR(svn_fs_make_dir(txn_root, "/zig/foo", pool));
5500   SVN_ERR(svn_fs_txn_prop(&txn_svn_date, txn, SVN_PROP_REVISION_DATE, pool));
5501   SVN_TEST_ASSERT(txn_svn_date);
5502   sleep_for_timestamps();
5503   SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool));
5504 
5505   SVN_ERR(svn_fs_revision_proplist(&proplist, fs, rev, pool));
5506   svn_date = apr_hash_get(proplist, SVN_PROP_REVISION_DATE,
5507                           APR_HASH_KEY_STRING);
5508   SVN_TEST_ASSERT(svn_date);
5509   SVN_TEST_ASSERT(!svn_string_compare(svn_date, txn_svn_date));
5510 
5511   /* Commit that instructs the backend to use a specific svn:date, but
5512    * doesn't provide one.  This used to fail with BDB prior to r1663697. */
5513   SVN_ERR(svn_fs_begin_txn2(&txn, fs, rev, SVN_FS_TXN_CLIENT_DATE, pool));
5514   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
5515   SVN_ERR(svn_fs_make_dir(txn_root, "/zig/bar", pool));
5516   SVN_ERR(svn_fs_txn_prop(&txn_svn_date, txn, SVN_PROP_REVISION_DATE, pool));
5517   SVN_TEST_ASSERT(txn_svn_date);
5518   sleep_for_timestamps();
5519   SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool));
5520 
5521   SVN_ERR(svn_fs_revision_proplist(&proplist, fs, rev, pool));
5522   svn_date = apr_hash_get(proplist, SVN_PROP_REVISION_DATE,
5523                           APR_HASH_KEY_STRING);
5524   SVN_TEST_ASSERT(svn_date);
5525   SVN_TEST_ASSERT(!svn_string_compare(svn_date, txn_svn_date));
5526 
5527   return SVN_NO_ERROR;
5528 }
5529 
5530 static svn_error_t *
test_compat_version(const svn_test_opts_t * opts,apr_pool_t * pool)5531 test_compat_version(const svn_test_opts_t *opts,
5532                     apr_pool_t *pool)
5533 {
5534   svn_version_t *compatible_version;
5535   apr_hash_t *config = apr_hash_make(pool);
5536 
5537   svn_version_t vcurrent = {SVN_VER_MAJOR, SVN_VER_MINOR, 0, ""};
5538   svn_version_t v1_2_0 = {1, 2, 0, ""};
5539   svn_version_t v1_3_0 = {1, 3, 0, ""};
5540   svn_version_t v1_5_0 = {1, 5, 0, ""};
5541 
5542   /* no version specified -> default to the current one */
5543   SVN_ERR(svn_fs__compatible_version(&compatible_version, config, pool));
5544   SVN_TEST_ASSERT(svn_ver_equal(compatible_version, &vcurrent));
5545 
5546   /* test specific compat option */
5547   svn_hash_sets(config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
5548   SVN_ERR(svn_fs__compatible_version(&compatible_version, config, pool));
5549   SVN_TEST_ASSERT(svn_ver_equal(compatible_version, &v1_5_0));
5550 
5551   /* test precedence amongst compat options */
5552   svn_hash_sets(config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE, "1");
5553   SVN_ERR(svn_fs__compatible_version(&compatible_version, config, pool));
5554   SVN_TEST_ASSERT(svn_ver_equal(compatible_version, &v1_5_0));
5555 
5556   svn_hash_sets(config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
5557   SVN_ERR(svn_fs__compatible_version(&compatible_version, config, pool));
5558   SVN_TEST_ASSERT(svn_ver_equal(compatible_version, &v1_3_0));
5559 
5560   /* precedence should work with the generic option as well */
5561   svn_hash_sets(config, SVN_FS_CONFIG_COMPATIBLE_VERSION, "1.4.17-??");
5562   SVN_ERR(svn_fs__compatible_version(&compatible_version, config, pool));
5563   SVN_TEST_ASSERT(svn_ver_equal(compatible_version, &v1_3_0));
5564 
5565   svn_hash_sets(config, SVN_FS_CONFIG_COMPATIBLE_VERSION, "1.2.3-no!");
5566   SVN_ERR(svn_fs__compatible_version(&compatible_version, config, pool));
5567   SVN_TEST_ASSERT(svn_ver_equal(compatible_version, &v1_2_0));
5568 
5569   /* test generic option alone */
5570   config = apr_hash_make(pool);
5571   svn_hash_sets(config, SVN_FS_CONFIG_COMPATIBLE_VERSION, "1.2.3-no!");
5572   SVN_ERR(svn_fs__compatible_version(&compatible_version, config, pool));
5573   SVN_TEST_ASSERT(svn_ver_equal(compatible_version, &v1_2_0));
5574 
5575   /* out of range values should be caped by the current tool version */
5576   svn_hash_sets(config, SVN_FS_CONFIG_COMPATIBLE_VERSION, "2.3.4-x");
5577   SVN_ERR(svn_fs__compatible_version(&compatible_version, config, pool));
5578   SVN_TEST_ASSERT(svn_ver_equal(compatible_version, &vcurrent));
5579 
5580   return SVN_NO_ERROR;
5581 }
5582 
5583 static svn_error_t *
dir_prop_merge(const svn_test_opts_t * opts,apr_pool_t * pool)5584 dir_prop_merge(const svn_test_opts_t *opts,
5585                apr_pool_t *pool)
5586 {
5587   svn_fs_t *fs;
5588   svn_revnum_t head_rev;
5589   svn_fs_root_t *root;
5590   svn_fs_txn_t *txn, *mid_txn, *top_txn, *sub_txn, *c_txn;
5591   svn_boolean_t is_bdb = strcmp(opts->fs_type, SVN_FS_TYPE_BDB) == 0;
5592 
5593   /* Create test repository. */
5594   SVN_ERR(svn_test__create_fs(&fs, "test-repo-fs-dir_prop-merge", opts,
5595                               pool));
5596 
5597   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
5598   SVN_ERR(svn_fs_txn_root(&root, txn, pool));
5599 
5600   /* Create and verify the greek tree. */
5601   SVN_ERR(svn_test__create_greek_tree(root, pool));
5602   SVN_ERR(test_commit_txn(&head_rev, txn, NULL, pool));
5603 
5604   /* Start concurrent transactions */
5605 
5606   /* 1st: modify a mid-level directory */
5607   SVN_ERR(svn_fs_begin_txn2(&mid_txn, fs, head_rev, 0, pool));
5608   SVN_ERR(svn_fs_txn_root(&root, mid_txn, pool));
5609   SVN_ERR(svn_fs_change_node_prop(root, "A/D", "test-prop",
5610                                   svn_string_create("val1", pool), pool));
5611   svn_fs_close_root(root);
5612 
5613   /* 2st: modify a top-level directory */
5614   SVN_ERR(svn_fs_begin_txn2(&top_txn, fs, head_rev, 0, pool));
5615   SVN_ERR(svn_fs_txn_root(&root, top_txn, pool));
5616   SVN_ERR(svn_fs_change_node_prop(root, "A", "test-prop",
5617                                   svn_string_create("val2", pool), pool));
5618   svn_fs_close_root(root);
5619 
5620   SVN_ERR(svn_fs_begin_txn2(&sub_txn, fs, head_rev, 0, pool));
5621   SVN_ERR(svn_fs_txn_root(&root, sub_txn, pool));
5622   SVN_ERR(svn_fs_change_node_prop(root, "A/D/G", "test-prop",
5623                                   svn_string_create("val3", pool), pool));
5624   svn_fs_close_root(root);
5625 
5626   /* 3rd: modify a conflicting change to the mid-level directory */
5627   SVN_ERR(svn_fs_begin_txn2(&c_txn, fs, head_rev, 0, pool));
5628   SVN_ERR(svn_fs_txn_root(&root, c_txn, pool));
5629   SVN_ERR(svn_fs_change_node_prop(root, "A/D", "test-prop",
5630                                   svn_string_create("valX", pool), pool));
5631   svn_fs_close_root(root);
5632 
5633   /* Prop changes to the same node should conflict */
5634   SVN_ERR(test_commit_txn(&head_rev, mid_txn, NULL, pool));
5635   SVN_ERR(test_commit_txn(&head_rev, c_txn, "/A/D", pool));
5636   SVN_ERR(svn_fs_abort_txn(c_txn, pool));
5637 
5638   /* Changes in a sub-tree should not conflict with prop changes to some
5639      parent directory but some backends are cleverer than others. */
5640   if (is_bdb)
5641     {
5642       SVN_ERR(test_commit_txn(&head_rev, top_txn, "/A", pool));
5643       SVN_ERR(svn_fs_abort_txn(top_txn, pool));
5644     }
5645   else
5646     {
5647       SVN_ERR(test_commit_txn(&head_rev, top_txn, NULL, pool));
5648     }
5649 
5650   /* The inverted case is not that trivial to handle.  Hence, conflict.
5651      Depending on the checking order, the reported conflict path differs. */
5652   SVN_ERR(test_commit_txn(&head_rev, sub_txn, is_bdb ? "/A/D" : "/A", pool));
5653   SVN_ERR(svn_fs_abort_txn(sub_txn, pool));
5654 
5655   return SVN_NO_ERROR;
5656 }
5657 
5658 static svn_error_t *
upgrade_while_committing(const svn_test_opts_t * opts,apr_pool_t * pool)5659 upgrade_while_committing(const svn_test_opts_t *opts,
5660                          apr_pool_t *pool)
5661 {
5662   svn_fs_t *fs;
5663   svn_revnum_t head_rev = 0;
5664   svn_fs_root_t *root;
5665   svn_fs_txn_t *txn1, *txn2;
5666   const char *fs_path;
5667   apr_hash_t *fs_config = apr_hash_make(pool);
5668 
5669   /* Bail (with success) on known-untestable scenarios */
5670   if (strcmp(opts->fs_type, "fsfs") != 0)
5671     return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
5672                             "this will test FSFS repositories only");
5673 
5674   if (opts->server_minor_version && (opts->server_minor_version < 6))
5675     return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
5676                             "pre-1.6 SVN doesn't support FSFS packing");
5677 
5678   /* Create test repository with greek tree. */
5679   fs_path = "test-repo-upgrade-while-committing";
5680 
5681   svn_hash_sets(fs_config, SVN_FS_CONFIG_COMPATIBLE_VERSION, "1.7");
5682   svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_SHARD_SIZE, "2");
5683   SVN_ERR(svn_test__create_fs2(&fs, fs_path, opts, fs_config, pool));
5684 
5685   SVN_ERR(svn_fs_begin_txn(&txn1, fs, head_rev, pool));
5686   SVN_ERR(svn_fs_txn_root(&root, txn1, pool));
5687   SVN_ERR(svn_test__create_greek_tree(root, pool));
5688   SVN_ERR(test_commit_txn(&head_rev, txn1, NULL, pool));
5689 
5690   /* Create txn with changes. */
5691   SVN_ERR(svn_fs_begin_txn(&txn1, fs, head_rev, pool));
5692   SVN_ERR(svn_fs_txn_root(&root, txn1, pool));
5693   SVN_ERR(svn_fs_make_dir(root, "/foo", pool));
5694 
5695   /* Upgrade filesystem, but keep existing svn_fs_t object. */
5696   SVN_ERR(svn_fs_upgrade(fs_path, pool));
5697 
5698   /* Creating a new txn for the old svn_fs_t should not fail. */
5699   SVN_ERR(svn_fs_begin_txn(&txn2, fs, head_rev, pool));
5700 
5701   /* Committing the already existing txn should not fail. */
5702   SVN_ERR(test_commit_txn(&head_rev, txn1, NULL, pool));
5703 
5704   /* Verify filesystem content. */
5705   SVN_ERR(svn_fs_verify(fs_path, NULL, 0, SVN_INVALID_REVNUM, NULL, NULL,
5706                         NULL, NULL, pool));
5707 
5708   return SVN_NO_ERROR;
5709 }
5710 
5711 /* Utility method for test_paths_changed. Verify that REV in FS changes
5712  * exactly one path and that that change is a property change.  Expect
5713  * the MERGEINFO_MOD flag of the change to have the given value.
5714  */
5715 static svn_error_t *
verify_root_prop_change(svn_fs_t * fs,svn_revnum_t rev,svn_tristate_t mergeinfo_mod,apr_pool_t * pool)5716 verify_root_prop_change(svn_fs_t *fs,
5717                         svn_revnum_t rev,
5718                         svn_tristate_t mergeinfo_mod,
5719                         apr_pool_t *pool)
5720 {
5721   svn_fs_path_change2_t *change;
5722   svn_fs_root_t *root;
5723   apr_hash_t *changes;
5724 
5725   SVN_ERR(svn_fs_revision_root(&root, fs, rev, pool));
5726   SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
5727   SVN_TEST_ASSERT(apr_hash_count(changes) == 1);
5728   change = svn_hash_gets(changes, "/");
5729 
5730   SVN_TEST_ASSERT(change->node_rev_id);
5731   SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_modify);
5732   SVN_TEST_ASSERT(   change->node_kind == svn_node_dir
5733                   || change->node_kind == svn_node_unknown);
5734   SVN_TEST_ASSERT(change->text_mod == FALSE);
5735   SVN_TEST_ASSERT(change->prop_mod == TRUE);
5736 
5737   if (change->copyfrom_known)
5738     {
5739       SVN_TEST_ASSERT(change->copyfrom_rev == SVN_INVALID_REVNUM);
5740       SVN_TEST_ASSERT(change->copyfrom_path == NULL);
5741     }
5742 
5743   SVN_TEST_ASSERT(change->mergeinfo_mod == mergeinfo_mod);
5744 
5745   return SVN_NO_ERROR;
5746 }
5747 
5748 static svn_error_t *
test_paths_changed(const svn_test_opts_t * opts,apr_pool_t * pool)5749 test_paths_changed(const svn_test_opts_t *opts,
5750                    apr_pool_t *pool)
5751 {
5752   svn_fs_t *fs;
5753   svn_revnum_t head_rev = 0;
5754   svn_fs_root_t *root;
5755   svn_fs_txn_t *txn;
5756   const char *fs_path;
5757   apr_hash_t *changes;
5758   svn_boolean_t has_mergeinfo_mod = FALSE;
5759   apr_hash_index_t *hi;
5760   int i;
5761 
5762   /* The "mergeinfo_mod flag will say "unknown" until recently. */
5763   if (   strcmp(opts->fs_type, SVN_FS_TYPE_BDB) != 0
5764       && (!opts->server_minor_version || (opts->server_minor_version >= 9)))
5765     has_mergeinfo_mod = TRUE;
5766 
5767   /* Create test repository with greek tree. */
5768   fs_path = "test-repo-paths-changed";
5769 
5770   SVN_ERR(svn_test__create_fs2(&fs, fs_path, opts, NULL, pool));
5771 
5772   SVN_ERR(svn_fs_begin_txn(&txn, fs, head_rev, pool));
5773   SVN_ERR(svn_fs_txn_root(&root, txn, pool));
5774   SVN_ERR(svn_test__create_greek_tree(root, pool));
5775   SVN_ERR(test_commit_txn(&head_rev, txn, NULL, pool));
5776 
5777   /* Create txns with various prop changes. */
5778   SVN_ERR(svn_fs_begin_txn(&txn, fs, head_rev, pool));
5779   SVN_ERR(svn_fs_txn_root(&root, txn, pool));
5780   SVN_ERR(svn_fs_change_node_prop(root, "/", "propname",
5781                                   svn_string_create("propval", pool), pool));
5782   SVN_ERR(test_commit_txn(&head_rev, txn, NULL, pool));
5783 
5784   SVN_ERR(svn_fs_begin_txn(&txn, fs, head_rev, pool));
5785   SVN_ERR(svn_fs_txn_root(&root, txn, pool));
5786   SVN_ERR(svn_fs_change_node_prop(root, "/", "svn:mergeinfo",
5787                                   svn_string_create("/: 1\n", pool), pool));
5788   SVN_ERR(test_commit_txn(&head_rev, txn, NULL, pool));
5789 
5790   /* Verify changed path lists. */
5791 
5792   /* Greek tree creation rev. */
5793   SVN_ERR(svn_fs_revision_root(&root, fs, head_rev - 2, pool));
5794   SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
5795 
5796   /* Reports all paths? */
5797   for (i = 0; svn_test__greek_tree_nodes[i].path; ++i)
5798     {
5799       const char *path
5800         = svn_fspath__canonicalize(svn_test__greek_tree_nodes[i].path, pool);
5801 
5802       SVN_TEST_ASSERT(svn_hash_gets(changes, path));
5803     }
5804 
5805   SVN_TEST_ASSERT(apr_hash_count(changes) == i);
5806 
5807   /* Verify per-path info. */
5808   for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
5809     {
5810       svn_fs_path_change2_t *change = apr_hash_this_val(hi);
5811 
5812       SVN_TEST_ASSERT(change->node_rev_id);
5813       SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_add);
5814       SVN_TEST_ASSERT(   change->node_kind == svn_node_file
5815                       || change->node_kind == svn_node_dir
5816                       || change->node_kind == svn_node_unknown);
5817 
5818       if (change->node_kind != svn_node_unknown)
5819         SVN_TEST_ASSERT(change->text_mod == (   change->node_kind
5820                                              == svn_node_file));
5821 
5822       SVN_TEST_ASSERT(change->prop_mod == FALSE);
5823 
5824       if (change->copyfrom_known)
5825         {
5826           SVN_TEST_ASSERT(change->copyfrom_rev == SVN_INVALID_REVNUM);
5827           SVN_TEST_ASSERT(change->copyfrom_path == NULL);
5828         }
5829 
5830       if (has_mergeinfo_mod)
5831         SVN_TEST_ASSERT(change->mergeinfo_mod == svn_tristate_false);
5832       else
5833         SVN_TEST_ASSERT(change->mergeinfo_mod == svn_tristate_unknown);
5834     }
5835 
5836   /* Propset rev. */
5837   SVN_ERR(verify_root_prop_change(fs, head_rev - 1,
5838                                   has_mergeinfo_mod ? svn_tristate_false
5839                                                     : svn_tristate_unknown,
5840                                   pool));
5841 
5842   /* Mergeinfo set rev. */
5843   SVN_ERR(verify_root_prop_change(fs, head_rev,
5844                                   has_mergeinfo_mod ? svn_tristate_true
5845                                                     : svn_tristate_unknown,
5846                                   pool));
5847 
5848   return SVN_NO_ERROR;
5849 }
5850 
5851 static svn_error_t *
test_delete_replaced_paths_changed(const svn_test_opts_t * opts,apr_pool_t * pool)5852 test_delete_replaced_paths_changed(const svn_test_opts_t *opts,
5853                                    apr_pool_t *pool)
5854 {
5855   svn_fs_t *fs;
5856   svn_revnum_t head_rev = 0;
5857   svn_fs_root_t *root;
5858   svn_fs_txn_t *txn;
5859   const char *fs_path;
5860   apr_hash_t *changes;
5861   svn_fs_path_change2_t *change;
5862   const svn_fs_id_t *file_id;
5863 
5864   /* Create test repository with greek tree. */
5865   fs_path = "test-repo-delete-replace-paths-changed";
5866 
5867   SVN_ERR(svn_test__create_fs2(&fs, fs_path, opts, NULL, pool));
5868 
5869   SVN_ERR(svn_fs_begin_txn(&txn, fs, head_rev, pool));
5870   SVN_ERR(svn_fs_txn_root(&root, txn, pool));
5871   SVN_ERR(svn_test__create_greek_tree(root, pool));
5872   SVN_ERR(test_commit_txn(&head_rev, txn, NULL, pool));
5873 
5874   /* Create that replaces a file with a folder and then deletes that
5875    * replacement.  Start with the deletion. */
5876   SVN_ERR(svn_fs_begin_txn(&txn, fs, head_rev, pool));
5877   SVN_ERR(svn_fs_txn_root(&root, txn, pool));
5878   SVN_ERR(svn_fs_delete(root, "/iota", pool));
5879 
5880   /* The change list should now report a deleted file. */
5881   SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
5882   change = svn_hash_gets(changes, "/iota");
5883   file_id = change->node_rev_id;
5884   SVN_TEST_ASSERT(   change->node_kind == svn_node_file
5885                   || change->node_kind == svn_node_unknown);
5886   SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_delete);
5887 
5888   /* Add a replacement. */
5889   SVN_ERR(svn_fs_make_dir(root, "/iota", pool));
5890 
5891   /* The change list now reports a replacement by a directory. */
5892   SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
5893   change = svn_hash_gets(changes, "/iota");
5894   SVN_TEST_ASSERT(   change->node_kind == svn_node_dir
5895                   || change->node_kind == svn_node_unknown);
5896   SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_replace);
5897   SVN_TEST_ASSERT(svn_fs_compare_ids(change->node_rev_id, file_id) != 0);
5898 
5899   /* Delete the replacement again. */
5900   SVN_ERR(svn_fs_delete(root, "/iota", pool));
5901 
5902   /* The change list should now be reported as a deleted file again. */
5903   SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
5904   change = svn_hash_gets(changes, "/iota");
5905   SVN_TEST_ASSERT(   change->node_kind == svn_node_file
5906                   || change->node_kind == svn_node_unknown);
5907   SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_delete);
5908   SVN_TEST_ASSERT(svn_fs_compare_ids(change->node_rev_id, file_id) == 0);
5909 
5910   /* Finally, commit the change. */
5911   SVN_ERR(test_commit_txn(&head_rev, txn, NULL, pool));
5912 
5913   /* The committed revision should still report the same change. */
5914   SVN_ERR(svn_fs_revision_root(&root, fs, head_rev, pool));
5915   SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
5916   change = svn_hash_gets(changes, "/iota");
5917   SVN_TEST_ASSERT(   change->node_kind == svn_node_file
5918                   || change->node_kind == svn_node_unknown);
5919   SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_delete);
5920 
5921   return SVN_NO_ERROR;
5922 }
5923 
5924 /* Get rid of transaction NAME in FS.  This function deals with backend-
5925  * specific behavior as permitted by the API. */
5926 static svn_error_t *
cleanup_txn(svn_fs_t * fs,const char * name,apr_pool_t * scratch_pool)5927 cleanup_txn(svn_fs_t *fs,
5928             const char *name,
5929             apr_pool_t *scratch_pool)
5930 {
5931   /* Get rid of the txns one at a time. */
5932   svn_error_t *err = svn_fs_purge_txn(fs, name, scratch_pool);
5933 
5934   /* Some backends (BDB) don't support purging transactions that have never
5935    * seen an abort or commit attempt.   Simply abort those txns. */
5936   if (err && err->apr_err == SVN_ERR_FS_TRANSACTION_NOT_DEAD)
5937     {
5938       svn_fs_txn_t *txn;
5939       svn_error_clear(err);
5940       err = SVN_NO_ERROR;
5941 
5942       SVN_ERR(svn_fs_open_txn(&txn, fs, name, scratch_pool));
5943       SVN_ERR(svn_fs_abort_txn(txn, scratch_pool));
5944 
5945       /* Should be gone now ... */
5946       SVN_TEST_ASSERT_ERROR(svn_fs_open_txn(&txn, fs, name, scratch_pool),
5947                             SVN_ERR_FS_NO_SUCH_TRANSACTION);
5948     }
5949 
5950   return svn_error_trace(err);
5951 }
5952 
5953 /* Make sure we get txn lists correctly. */
5954 static svn_error_t *
purge_txn_test(const svn_test_opts_t * opts,apr_pool_t * pool)5955 purge_txn_test(const svn_test_opts_t *opts,
5956                apr_pool_t *pool)
5957 {
5958   svn_fs_t *fs;
5959   svn_fs_txn_t *txn;
5960   const char *name1, *name2;
5961   apr_array_header_t *txn_list;
5962   apr_pool_t *subpool = svn_pool_create(pool);
5963 
5964   SVN_ERR(svn_test__create_fs(&fs, "test-repo-purge-txn",
5965                               opts, pool));
5966 
5967   /* Begin a new transaction, get its name (in the top pool), close it.  */
5968   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
5969   SVN_ERR(svn_fs_txn_name(&name1, txn, pool));
5970 
5971   /* Begin *another* transaction, get its name (in the top pool), close it.  */
5972   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
5973   SVN_ERR(svn_fs_txn_name(&name2, txn, pool));
5974   svn_pool_clear(subpool);
5975 
5976   /* Get rid of the txns one at a time. */
5977   SVN_ERR(cleanup_txn(fs, name1, pool));
5978 
5979   /* There should be exactly one left. */
5980   SVN_ERR(svn_fs_list_transactions(&txn_list, fs, pool));
5981 
5982   /* Check the list. It should have *exactly* one entry. */
5983   SVN_TEST_ASSERT(   txn_list->nelts == 1
5984                   && !strcmp(name2, APR_ARRAY_IDX(txn_list, 0, const char *)));
5985 
5986   /* Get rid of the other txn as well. */
5987   SVN_ERR(cleanup_txn(fs, name2, pool));
5988 
5989   /* There should be exactly one left. */
5990   SVN_ERR(svn_fs_list_transactions(&txn_list, fs, pool));
5991 
5992   /* Check the list. It should have no entries. */
5993   SVN_TEST_ASSERT(txn_list->nelts == 0);
5994 
5995   return SVN_NO_ERROR;
5996 }
5997 
5998 /* Test svn_fs_{contents,props}_{different,changed}().
5999  * ### This currently only tests them on revision roots, not on txn roots.
6000  */
6001 static svn_error_t *
compare_contents(const svn_test_opts_t * opts,apr_pool_t * pool)6002 compare_contents(const svn_test_opts_t *opts,
6003                  apr_pool_t *pool)
6004 {
6005   svn_fs_t *fs;
6006   svn_fs_txn_t *txn;
6007   svn_fs_root_t *txn_root, *root1, *root2;
6008   const char *original = "original contents";
6009   svn_revnum_t rev;
6010   int i;
6011   apr_pool_t *iterpool = svn_pool_create(pool);
6012   svn_boolean_t changed;
6013 
6014   /* Two similar but different texts that yield the same MD5 digest. */
6015   const char *evil_text1
6016     = "\xd1\x31\xdd\x02\xc5\xe6\xee\xc4\x69\x3d\x9a\x06\x98\xaf\xf9\x5c"
6017       "\x2f\xca\xb5\x87\x12\x46\x7e\xab\x40\x04\x58\x3e\xb8\xfb\x7f\x89"
6018       "\x55\xad\x34\x06\x09\xf4\xb3\x02\x83\xe4\x88\x83\x25\x71\x41\x5a"
6019       "\x08\x51\x25\xe8\xf7\xcd\xc9\x9f\xd9\x1d\xbd\xf2\x80\x37\x3c\x5b"
6020       "\xd8\x82\x3e\x31\x56\x34\x8f\x5b\xae\x6d\xac\xd4\x36\xc9\x19\xc6"
6021       "\xdd\x53\xe2\xb4\x87\xda\x03\xfd\x02\x39\x63\x06\xd2\x48\xcd\xa0"
6022       "\xe9\x9f\x33\x42\x0f\x57\x7e\xe8\xce\x54\xb6\x70\x80\xa8\x0d\x1e"
6023       "\xc6\x98\x21\xbc\xb6\xa8\x83\x93\x96\xf9\x65\x2b\x6f\xf7\x2a\x70";
6024   const char *evil_text2
6025     = "\xd1\x31\xdd\x02\xc5\xe6\xee\xc4\x69\x3d\x9a\x06\x98\xaf\xf9\x5c"
6026       "\x2f\xca\xb5\x07\x12\x46\x7e\xab\x40\x04\x58\x3e\xb8\xfb\x7f\x89"
6027       "\x55\xad\x34\x06\x09\xf4\xb3\x02\x83\xe4\x88\x83\x25\xf1\x41\x5a"
6028       "\x08\x51\x25\xe8\xf7\xcd\xc9\x9f\xd9\x1d\xbd\x72\x80\x37\x3c\x5b"
6029       "\xd8\x82\x3e\x31\x56\x34\x8f\x5b\xae\x6d\xac\xd4\x36\xc9\x19\xc6"
6030       "\xdd\x53\xe2\x34\x87\xda\x03\xfd\x02\x39\x63\x06\xd2\x48\xcd\xa0"
6031       "\xe9\x9f\x33\x42\x0f\x57\x7e\xe8\xce\x54\xb6\x70\x80\x28\x0d\x1e"
6032       "\xc6\x98\x21\xbc\xb6\xa8\x83\x93\x96\xf9\x65\xab\x6f\xf7\x2a\x70";
6033   svn_checksum_t *checksum1, *checksum2;
6034 
6035   /* (path, rev) pairs to compare plus the expected API return values */
6036   struct
6037     {
6038       svn_revnum_t rev1;
6039       const char *path1;
6040       svn_revnum_t rev2;
6041       const char *path2;
6042 
6043       svn_boolean_t different;  /* result of svn_fs_*_different */
6044       svn_tristate_t changed;   /* result of svn_fs_*_changed */
6045     } to_compare[] =
6046     {
6047       /* same representation */
6048       { 1, "foo", 2, "foo", FALSE, svn_tristate_false },
6049       { 1, "foo", 2, "bar", FALSE, svn_tristate_false },
6050       { 2, "foo", 2, "bar", FALSE, svn_tristate_false },
6051 
6052       /* different content but MD5 check is not reliable */
6053       { 3, "foo", 3, "bar", TRUE, svn_tristate_true },
6054 
6055       /* different contents */
6056       { 1, "foo", 3, "bar", TRUE, svn_tristate_true },
6057       { 1, "foo", 3, "foo", TRUE, svn_tristate_true },
6058       { 3, "foo", 4, "bar", TRUE, svn_tristate_true },
6059       { 3, "foo", 4, "bar", TRUE, svn_tristate_true },
6060       { 2, "bar", 3, "bar", TRUE, svn_tristate_true },
6061       { 3, "bar", 4, "bar", TRUE, svn_tristate_true },
6062 
6063       /* variations on the same theme: same content, possibly different rep */
6064       { 4, "foo", 4, "bar", FALSE, svn_tristate_unknown },
6065       { 1, "foo", 4, "bar", FALSE, svn_tristate_unknown },
6066       { 2, "foo", 4, "bar", FALSE, svn_tristate_unknown },
6067       { 1, "foo", 4, "foo", FALSE, svn_tristate_unknown },
6068       { 2, "foo", 4, "foo", FALSE, svn_tristate_unknown },
6069       { 2, "bar", 4, "bar", FALSE, svn_tristate_unknown },
6070 
6071       /* EOL */
6072       { 0 },
6073     };
6074 
6075   /* Same same, but different.
6076    * Just checking that we actually have an MD5 collision. */
6077   SVN_ERR(svn_checksum(&checksum1, svn_checksum_md5, evil_text1,
6078                        strlen(evil_text1), pool));
6079   SVN_ERR(svn_checksum(&checksum2, svn_checksum_md5, evil_text2,
6080                        strlen(evil_text2), pool));
6081   SVN_TEST_ASSERT(svn_checksum_match(checksum1, checksum1));
6082   SVN_TEST_ASSERT(strcmp(evil_text1, evil_text2));
6083 
6084   /* Now, build up our test repo. */
6085   SVN_ERR(svn_test__create_fs(&fs, "test-repo-compare-contents",
6086                               opts, pool));
6087 
6088   /* Rev 1: create a file. */
6089   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, iterpool));
6090   SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool));
6091   SVN_ERR(svn_fs_make_file(txn_root, "foo", iterpool));
6092   SVN_ERR(svn_test__set_file_contents(txn_root, "foo", original, iterpool));
6093   SVN_ERR(svn_fs_change_node_prop(txn_root, "foo", "prop",
6094                                   svn_string_create(original, iterpool),
6095                                   iterpool));
6096   SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, iterpool));
6097   SVN_TEST_ASSERT(rev == 1);
6098   svn_pool_clear(iterpool);
6099 
6100   /* Rev 2: copy that file. */
6101   SVN_ERR(svn_fs_revision_root(&root1, fs, rev, iterpool));
6102   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, iterpool));
6103   SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool));
6104   SVN_ERR(svn_fs_copy(root1, "foo", txn_root, "bar", iterpool));
6105   SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, iterpool));
6106   SVN_TEST_ASSERT(rev == 2);
6107   svn_pool_clear(iterpool);
6108 
6109   /* Rev 3: modify both files.
6110    * The new contents differs for both files but has the same length and MD5.
6111    */
6112   SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, iterpool));
6113   SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool));
6114   SVN_ERR(svn_test__set_file_contents(txn_root, "foo", evil_text1, iterpool));
6115   SVN_ERR(svn_test__set_file_contents(txn_root, "bar", evil_text2, iterpool));
6116   SVN_ERR(svn_fs_change_node_prop(txn_root, "foo", "prop",
6117                                   svn_string_create(evil_text1, iterpool),
6118                                   iterpool));
6119   SVN_ERR(svn_fs_change_node_prop(txn_root, "bar", "prop",
6120                                   svn_string_create(evil_text2, iterpool),
6121                                   iterpool));
6122   SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, iterpool));
6123   SVN_TEST_ASSERT(rev == 3);
6124   svn_pool_clear(iterpool);
6125 
6126   /* Rev 4: revert both file contents.
6127    */
6128   SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, iterpool));
6129   SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool));
6130   SVN_ERR(svn_test__set_file_contents(txn_root, "foo", original, iterpool));
6131   SVN_ERR(svn_test__set_file_contents(txn_root, "bar", original, iterpool));
6132   SVN_ERR(svn_fs_change_node_prop(txn_root, "foo", "prop",
6133                                   svn_string_create(original, iterpool),
6134                                   iterpool));
6135   SVN_ERR(svn_fs_change_node_prop(txn_root, "bar", "prop",
6136                                   svn_string_create(original, iterpool),
6137                                   iterpool));
6138   SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, iterpool));
6139   SVN_TEST_ASSERT(rev == 4);
6140   svn_pool_clear(iterpool);
6141 
6142   /* Perform all comparisons listed in TO_COMPARE. */
6143   for (i = 0; to_compare[i].rev1 > 0; ++i)
6144     {
6145       svn_boolean_t text_different;
6146       svn_boolean_t text_changed;
6147       svn_boolean_t props_different;
6148       svn_boolean_t props_changed;
6149 
6150       svn_pool_clear(iterpool);
6151       SVN_ERR(svn_fs_revision_root(&root1, fs, to_compare[i].rev1, iterpool));
6152       SVN_ERR(svn_fs_revision_root(&root2, fs, to_compare[i].rev2, iterpool));
6153 
6154       /* Compare node texts. */
6155       SVN_ERR(svn_fs_contents_different(&text_different,
6156                                         root1, to_compare[i].path1,
6157                                         root2, to_compare[i].path2,
6158                                         iterpool));
6159       SVN_ERR(svn_fs_contents_changed(&text_changed,
6160                                       root1, to_compare[i].path1,
6161                                       root2, to_compare[i].path2,
6162                                       iterpool));
6163 
6164       /* Compare properties. */
6165       SVN_ERR(svn_fs_props_different(&props_different,
6166                                      root1, to_compare[i].path1,
6167                                      root2, to_compare[i].path2,
6168                                      iterpool));
6169       SVN_ERR(svn_fs_props_changed(&props_changed,
6170                                    root1, to_compare[i].path1,
6171                                    root2, to_compare[i].path2,
6172                                    iterpool));
6173 
6174       /* Check results. */
6175       SVN_TEST_ASSERT(text_different == to_compare[i].different);
6176       SVN_TEST_ASSERT(props_different == to_compare[i].different);
6177 
6178       switch (to_compare[i].changed)
6179         {
6180         case svn_tristate_true:
6181           SVN_TEST_ASSERT(text_changed);
6182           SVN_TEST_ASSERT(props_changed);
6183           break;
6184 
6185         case svn_tristate_false:
6186           SVN_TEST_ASSERT(!text_changed);
6187           SVN_TEST_ASSERT(!props_changed);
6188           break;
6189 
6190         default:
6191           break;
6192         }
6193     }
6194 
6195   /* Check how svn_fs_contents_different() and svn_fs_contents_changed()
6196      handles invalid path.*/
6197   SVN_ERR(svn_fs_revision_root(&root1, fs, 1, iterpool));
6198   SVN_TEST_ASSERT_ANY_ERROR(
6199     svn_fs_contents_changed(&changed, root1, "/", root1, "/", iterpool));
6200   SVN_TEST_ASSERT_ANY_ERROR(
6201     svn_fs_contents_different(&changed, root1, "/", root1, "/", iterpool));
6202 
6203   SVN_TEST_ASSERT_ANY_ERROR(
6204     svn_fs_contents_changed(&changed, root1, "/non-existent", root1,
6205                             "/non-existent", iterpool));
6206   SVN_TEST_ASSERT_ANY_ERROR(
6207     svn_fs_contents_different(&changed, root1, "/non-existent", root1,
6208                               "/non-existent", iterpool));
6209 
6210   svn_pool_destroy(iterpool);
6211 
6212   return SVN_NO_ERROR;
6213 }
6214 
6215 static svn_error_t *
test_path_change_create(const svn_test_opts_t * opts,apr_pool_t * pool)6216 test_path_change_create(const svn_test_opts_t *opts,
6217                         apr_pool_t *pool)
6218 {
6219   svn_fs_t *fs;
6220   svn_fs_root_t *root;
6221   const svn_fs_id_t *id;
6222   svn_fs_path_change2_t *change;
6223 
6224   /* Build an empty test repo ... */
6225   SVN_ERR(svn_test__create_fs(&fs, "test-repo-path-change-create",
6226                               opts, pool));
6227 
6228   /* ... just to give us a valid ID. */
6229   SVN_ERR(svn_fs_revision_root(&root, fs, 0, pool));
6230   SVN_ERR(svn_fs_node_id(&id, root, "", pool));
6231 
6232   /* Do what we came here for. */
6233   change = svn_fs_path_change2_create(id, svn_fs_path_change_replace, pool);
6234 
6235   SVN_TEST_ASSERT(change);
6236   SVN_TEST_ASSERT(change->node_rev_id == id);
6237   SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_replace);
6238 
6239   /* All other fields should be "empty" / "unused". */
6240   SVN_TEST_ASSERT(change->node_kind == svn_node_none);
6241 
6242   SVN_TEST_ASSERT(change->text_mod == FALSE);
6243   SVN_TEST_ASSERT(change->prop_mod == FALSE);
6244   SVN_TEST_ASSERT(change->mergeinfo_mod == svn_tristate_unknown);
6245 
6246   SVN_TEST_ASSERT(change->copyfrom_known == FALSE);
6247   SVN_TEST_ASSERT(change->copyfrom_rev == SVN_INVALID_REVNUM);
6248   SVN_TEST_ASSERT(change->copyfrom_path == NULL);
6249 
6250   return SVN_NO_ERROR;
6251 }
6252 
6253 static svn_error_t *
test_node_created_info(const svn_test_opts_t * opts,apr_pool_t * pool)6254 test_node_created_info(const svn_test_opts_t *opts,
6255                        apr_pool_t *pool)
6256 {
6257   svn_fs_t *fs;
6258   svn_fs_txn_t *txn;
6259   svn_fs_root_t *txn_root, *root;
6260   svn_revnum_t rev;
6261   int i;
6262   apr_pool_t *iterpool = svn_pool_create(pool);
6263 
6264   /* Test vectors. */
6265   struct
6266     {
6267       svn_revnum_t rev;
6268       const char *path;
6269       svn_revnum_t crev;
6270       const char *cpath;
6271     } to_check[] =
6272     {
6273       /* New noderev only upon modification. */
6274       { 1, "A/B/E/beta",  1, "/A/B/E/beta" },
6275       { 2, "A/B/E/beta",  1, "/A/B/E/beta" },
6276       { 3, "A/B/E/beta",  3, "/A/B/E/beta" },
6277       { 4, "A/B/E/beta",  3, "/A/B/E/beta" },
6278 
6279       /* Lazily copied node. */
6280       { 2, "Z/B/E/beta",  1, "/A/B/E/beta" },
6281       { 3, "Z/B/E/beta",  1, "/A/B/E/beta" },
6282       { 4, "Z/B/E/beta",  4, "/Z/B/E/beta" },
6283 
6284       /* Bubble-up upon sub-tree change. */
6285       { 2, "Z",  2, "/Z" },
6286       { 3, "Z",  2, "/Z" },
6287       { 4, "Z",  4, "/Z" },
6288 
6289       { 0 }
6290     };
6291 
6292   /* Start with a new repo and the greek tree in rev 1. */
6293   SVN_ERR(svn_test__create_fs(&fs, "test-repo-node-created-path",
6294                               opts, pool));
6295 
6296   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, iterpool));
6297   SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool));
6298   SVN_ERR(svn_test__create_greek_tree(txn_root, iterpool));
6299   SVN_ERR(test_commit_txn(&rev, txn, NULL, iterpool));
6300   svn_pool_clear(iterpool);
6301 
6302   /* r2: copy a subtree */
6303   SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, iterpool));
6304   SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool));
6305   SVN_ERR(svn_fs_revision_root(&root, fs, rev, iterpool));
6306   SVN_ERR(svn_fs_copy(root, "A", txn_root, "Z", iterpool));
6307   SVN_ERR(test_commit_txn(&rev, txn, NULL, iterpool));
6308   svn_pool_clear(iterpool);
6309 
6310   /* r3: touch node in copy source */
6311   SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, iterpool));
6312   SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool));
6313   SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/beta", "new", iterpool));
6314   SVN_ERR(test_commit_txn(&rev, txn, NULL, iterpool));
6315   svn_pool_clear(iterpool);
6316 
6317   /* r4: touch same relative node in copy target */
6318   SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, iterpool));
6319   SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool));
6320   SVN_ERR(svn_test__set_file_contents(txn_root, "Z/B/E/beta", "new", iterpool));
6321   SVN_ERR(test_commit_txn(&rev, txn, NULL, iterpool));
6322   svn_pool_clear(iterpool);
6323 
6324   /* Now ask for some 'node created' info. */
6325   for (i = 0; to_check[i].rev > 0; ++i)
6326     {
6327       svn_revnum_t crev;
6328       const char *cpath;
6329 
6330       svn_pool_clear(iterpool);
6331 
6332       /* Get created path and rev. */
6333       SVN_ERR(svn_fs_revision_root(&root, fs, to_check[i].rev, iterpool));
6334       SVN_ERR(svn_fs_node_created_path(&cpath, root, to_check[i].path,
6335                                        iterpool));
6336       SVN_ERR(svn_fs_node_created_rev(&crev, root, to_check[i].path,
6337                                       iterpool));
6338 
6339       /* Compare the results with our expectations. */
6340       SVN_TEST_STRING_ASSERT(cpath, to_check[i].cpath);
6341 
6342       if (crev != to_check[i].crev)
6343         return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
6344                                  "created rev mismatch for %s@%ld:\n"
6345                                  "  expected '%ld'\n"
6346                                  "     found '%ld",
6347                                  to_check[i].path,
6348                                  to_check[i].rev,
6349                                  to_check[i].crev,
6350                                  crev);
6351     }
6352 
6353   svn_pool_destroy(iterpool);
6354 
6355   return SVN_NO_ERROR;
6356 }
6357 
6358 static svn_error_t *
test_print_modules(const svn_test_opts_t * opts,apr_pool_t * pool)6359 test_print_modules(const svn_test_opts_t *opts,
6360                    apr_pool_t *pool)
6361 {
6362   const char *expected, *module_name;
6363   svn_stringbuf_t *modules = svn_stringbuf_create_empty(pool);
6364 
6365   /* Name of the providing module */
6366   if (strcmp(opts->fs_type, SVN_FS_TYPE_FSX) == 0)
6367     module_name = "fs_x";
6368   else if (strcmp(opts->fs_type, SVN_FS_TYPE_FSFS) == 0)
6369     module_name = "fs_fs";
6370   else if (strcmp(opts->fs_type, SVN_FS_TYPE_BDB) == 0)
6371     module_name = "fs_base";
6372   else
6373     return svn_error_createf(SVN_ERR_TEST_SKIPPED, NULL,
6374                              "don't know the module name for %s",
6375                              opts->fs_type);
6376 
6377   SVN_ERR(svn_fs_print_modules(modules, pool));
6378 
6379   /* The requested FS type must be listed amongst the available modules. */
6380   expected = apr_psprintf(pool, "* %s : ", module_name);
6381   SVN_TEST_ASSERT(strstr(modules->data, expected));
6382 
6383   return SVN_NO_ERROR;
6384 }
6385 
6386 /* Baton to be used with process_file_contents. */
6387 typedef struct process_file_contents_baton_t
6388 {
6389   const char *contents;
6390   svn_boolean_t processed;
6391 } process_file_contents_baton_t;
6392 
6393 /* Implements svn_fs_process_contents_func_t.
6394  * We flag the BATON as "processed" and compare the CONTENTS we've got to
6395  * what we expect through the BATON.
6396  */
6397 static svn_error_t *
process_file_contents(const unsigned char * contents,apr_size_t len,void * baton,apr_pool_t * scratch_pool)6398 process_file_contents(const unsigned char *contents,
6399                       apr_size_t len,
6400                       void *baton,
6401                       apr_pool_t *scratch_pool)
6402 {
6403   process_file_contents_baton_t *b = baton;
6404 
6405   SVN_TEST_ASSERT(strlen(b->contents) == len);
6406   SVN_TEST_ASSERT(memcmp(b->contents, contents, len) == 0);
6407   b->processed = TRUE;
6408 
6409   return SVN_NO_ERROR;
6410 }
6411 
6412 static svn_error_t *
test_zero_copy_processsing(const svn_test_opts_t * opts,apr_pool_t * pool)6413 test_zero_copy_processsing(const svn_test_opts_t *opts,
6414                            apr_pool_t *pool)
6415 {
6416   svn_fs_t *fs;
6417   svn_fs_txn_t *txn;
6418   svn_fs_root_t *txn_root, *root;
6419   svn_revnum_t rev;
6420   const struct svn_test__tree_entry_t *node;
6421   apr_pool_t *iterpool = svn_pool_create(pool);
6422 
6423   /* Start with a new repo and the greek tree in rev 1. */
6424   SVN_ERR(svn_test__create_fs(&fs, "test-repo-zero-copy-processing",
6425                               opts, pool));
6426 
6427   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, iterpool));
6428   SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool));
6429   SVN_ERR(svn_test__create_greek_tree(txn_root, iterpool));
6430   SVN_ERR(test_commit_txn(&rev, txn, NULL, iterpool));
6431   svn_pool_clear(iterpool);
6432 
6433   SVN_ERR(svn_fs_revision_root(&root, fs, rev, pool));
6434 
6435   /* Prime the full-text cache by reading all file contents. */
6436   for (node = svn_test__greek_tree_nodes; node->path; node++)
6437     if (node->contents)
6438       {
6439         svn_stream_t *stream;
6440         svn_pool_clear(iterpool);
6441 
6442         SVN_ERR(svn_fs_file_contents(&stream, root, node->path, iterpool));
6443         SVN_ERR(svn_stream_copy3(stream, svn_stream_buffered(iterpool),
6444                                 NULL, NULL, iterpool));
6445       }
6446 
6447   /* Now, try to get the data directly from cache
6448    * (if the backend has caches). */
6449   for (node = svn_test__greek_tree_nodes; node->path; node++)
6450     if (node->contents)
6451       {
6452         svn_boolean_t success;
6453 
6454         process_file_contents_baton_t baton;
6455         baton.contents = node->contents;
6456         baton.processed = FALSE;
6457 
6458         svn_pool_clear(iterpool);
6459 
6460         SVN_ERR(svn_fs_try_process_file_contents(&success, root, node->path,
6461                                                 process_file_contents, &baton,
6462                                                 iterpool));
6463         SVN_TEST_ASSERT(success == baton.processed);
6464       }
6465 
6466   svn_pool_destroy(iterpool);
6467 
6468   return SVN_NO_ERROR;
6469 }
6470 
6471 static svn_error_t *
test_dir_optimal_order(const svn_test_opts_t * opts,apr_pool_t * pool)6472 test_dir_optimal_order(const svn_test_opts_t *opts,
6473                        apr_pool_t *pool)
6474 {
6475   svn_fs_t *fs;
6476   svn_fs_txn_t *txn;
6477   svn_fs_root_t *txn_root, *root;
6478   svn_revnum_t rev;
6479   apr_hash_t *unordered;
6480   apr_array_header_t *ordered;
6481   int i;
6482   apr_hash_index_t *hi;
6483 
6484   /* Create a new repo and the greek tree in rev 1. */
6485   SVN_ERR(svn_test__create_fs(&fs, "test-repo-dir-optimal-order",
6486                               opts, pool));
6487 
6488   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
6489   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
6490   SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
6491   SVN_ERR(test_commit_txn(&rev, txn, NULL, pool));
6492 
6493   SVN_ERR(svn_fs_revision_root(&root, fs, rev, pool));
6494 
6495   /* Call the API function we are interested in. */
6496   SVN_ERR(svn_fs_dir_entries(&unordered, root, "A", pool));
6497   SVN_ERR(svn_fs_dir_optimal_order(&ordered, root, unordered, pool, pool));
6498 
6499   /* Verify that all entries are returned. */
6500   SVN_TEST_ASSERT(ordered->nelts == apr_hash_count(unordered));
6501   for (hi = apr_hash_first(pool, unordered); hi; hi = apr_hash_next(hi))
6502     {
6503       svn_boolean_t found = FALSE;
6504       const char *name = apr_hash_this_key(hi);
6505 
6506       /* Compare hash -> array because the array might contain the same
6507        * entry more than once.  Since that can't happen in the hash, doing
6508        * it in this direction ensures ORDERED won't contain duplicates.
6509        */
6510       for (i = 0; !found && i < ordered->nelts; ++i)
6511         {
6512           svn_fs_dirent_t *item = APR_ARRAY_IDX(ordered, i, svn_fs_dirent_t*);
6513           if (strcmp(item->name, name) == 0)
6514             {
6515               found = TRUE;
6516               SVN_TEST_ASSERT(item == apr_hash_this_val(hi));
6517             }
6518         }
6519 
6520       SVN_TEST_ASSERT(found);
6521     }
6522 
6523   return SVN_NO_ERROR;
6524 }
6525 
6526 static svn_error_t *
test_config_files(const svn_test_opts_t * opts,apr_pool_t * pool)6527 test_config_files(const svn_test_opts_t *opts,
6528                   apr_pool_t *pool)
6529 {
6530   svn_fs_t *fs;
6531   apr_array_header_t *files;
6532   int i;
6533   const char *repo_name = "test-repo-config-files";
6534 
6535   /* Create a empty and get its config files. */
6536   SVN_ERR(svn_test__create_fs(&fs, repo_name, opts, pool));
6537   SVN_ERR(svn_fs_info_config_files(&files, fs, pool, pool));
6538 
6539   /* All files should exist and be below the repo. */
6540   for (i = 0; i < files->nelts; ++i)
6541     {
6542       svn_node_kind_t kind;
6543       const char *path = APR_ARRAY_IDX(files, i, const char*);
6544 
6545       SVN_ERR(svn_io_check_path(path, &kind, pool));
6546 
6547       SVN_TEST_ASSERT(kind == svn_node_file);
6548       SVN_TEST_ASSERT(svn_dirent_is_ancestor(repo_name, path));
6549     }
6550 
6551   return SVN_NO_ERROR;
6552 }
6553 
6554 static svn_error_t *
test_delta_file_stream(const svn_test_opts_t * opts,apr_pool_t * pool)6555 test_delta_file_stream(const svn_test_opts_t *opts,
6556                        apr_pool_t *pool)
6557 {
6558   svn_fs_t *fs;
6559   svn_fs_txn_t *txn;
6560   svn_fs_root_t *txn_root, *root1, *root2;
6561   svn_revnum_t rev;
6562   apr_pool_t *subpool = svn_pool_create(pool);
6563 
6564   const char *old_content = "some content";
6565   const char *new_content = "some more content";
6566   svn_txdelta_window_handler_t delta_handler;
6567   void *delta_baton;
6568   svn_txdelta_stream_t *delta_stream;
6569   svn_stringbuf_t *source = svn_stringbuf_create_empty(pool);
6570   svn_stringbuf_t *dest = svn_stringbuf_create_empty(pool);
6571 
6572   /* Create a new repo. */
6573   SVN_ERR(svn_test__create_fs(&fs, "test-repo-delta-file-stream",
6574                               opts, pool));
6575 
6576   /* Revision 1: create a file. */
6577   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
6578   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
6579   SVN_ERR(svn_fs_make_file(txn_root, "foo", pool));
6580   SVN_ERR(svn_test__set_file_contents(txn_root, "foo", old_content, pool));
6581   SVN_ERR(test_commit_txn(&rev, txn, NULL, pool));
6582 
6583   /* Revision 2: create a file. */
6584   SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, pool));
6585   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
6586   SVN_ERR(svn_test__set_file_contents(txn_root, "foo", new_content, pool));
6587   SVN_ERR(test_commit_txn(&rev, txn, NULL, pool));
6588 
6589   SVN_ERR(svn_fs_revision_root(&root1, fs, 1, pool));
6590   SVN_ERR(svn_fs_revision_root(&root2, fs, 2, pool));
6591 
6592   /* Test 1: Get delta against empty target. */
6593   SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream,
6594                                        NULL, NULL, root1, "foo", subpool));
6595 
6596   svn_stringbuf_setempty(source);
6597   svn_stringbuf_setempty(dest);
6598 
6599   svn_txdelta_apply(svn_stream_from_stringbuf(source, subpool),
6600                     svn_stream_from_stringbuf(dest, subpool),
6601                     NULL, NULL, subpool, &delta_handler, &delta_baton);
6602   SVN_ERR(svn_txdelta_send_txstream(delta_stream,
6603                                     delta_handler,
6604                                     delta_baton,
6605                                     subpool));
6606   SVN_TEST_STRING_ASSERT(old_content, dest->data);
6607   svn_pool_clear(subpool);
6608 
6609   /* Test 2: Get delta against previous version. */
6610   SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream,
6611                                        root1, "foo", root2, "foo", subpool));
6612 
6613   svn_stringbuf_set(source, old_content);
6614   svn_stringbuf_setempty(dest);
6615 
6616   svn_txdelta_apply(svn_stream_from_stringbuf(source, subpool),
6617                     svn_stream_from_stringbuf(dest, subpool),
6618                     NULL, NULL, subpool, &delta_handler, &delta_baton);
6619   SVN_ERR(svn_txdelta_send_txstream(delta_stream,
6620                                     delta_handler,
6621                                     delta_baton,
6622                                     subpool));
6623   SVN_TEST_STRING_ASSERT(new_content, dest->data);
6624   svn_pool_clear(subpool);
6625 
6626   /* Test 3: Get reverse delta. */
6627   SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream,
6628                                        root2, "foo", root1, "foo", subpool));
6629 
6630   svn_stringbuf_set(source, new_content);
6631   svn_stringbuf_setempty(dest);
6632 
6633   svn_txdelta_apply(svn_stream_from_stringbuf(source, subpool),
6634                     svn_stream_from_stringbuf(dest, subpool),
6635                     NULL, NULL, subpool, &delta_handler, &delta_baton);
6636   SVN_ERR(svn_txdelta_send_txstream(delta_stream,
6637                                     delta_handler,
6638                                     delta_baton,
6639                                     subpool));
6640   SVN_TEST_STRING_ASSERT(old_content, dest->data);
6641 
6642   svn_pool_destroy(subpool);
6643 
6644   return SVN_NO_ERROR;
6645 }
6646 
6647 static svn_error_t *
test_fs_merge(const svn_test_opts_t * opts,apr_pool_t * pool)6648 test_fs_merge(const svn_test_opts_t *opts,
6649               apr_pool_t *pool)
6650 {
6651   svn_fs_t *fs;
6652   svn_fs_txn_t *txn;
6653   svn_fs_root_t *txn_root, *root0, *root1;
6654   svn_revnum_t rev;
6655 
6656   /* Very basic test for svn_fs_merge because all the other interesting
6657    * cases get tested implicitly with concurrent txn / commit tests.
6658    * This API is just a thin layer around the internal merge function
6659    * and we simply check that the plumbing between them works.
6660    */
6661 
6662   /* Create a new repo. */
6663   SVN_ERR(svn_test__create_fs(&fs, "test-repo-fs-merge",
6664                               opts, pool));
6665   SVN_ERR(svn_fs_revision_root(&root0, fs, 0, pool));
6666 
6667   /* Revision 1: create a file. */
6668   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
6669   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
6670   SVN_ERR(svn_fs_make_file(txn_root, "foo", pool));
6671   SVN_ERR(test_commit_txn(&rev, txn, NULL, pool));
6672   SVN_ERR(svn_fs_revision_root(&root1, fs, rev, pool));
6673 
6674   /* Merge-able txn: create another file. */
6675   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
6676   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
6677   SVN_ERR(svn_fs_make_file(txn_root, "bar", pool));
6678 
6679   SVN_ERR(svn_fs_merge(NULL, root1, "/", txn_root, "/", root0, "/", pool));
6680 
6681   /* Not merge-able: create the same file file. */
6682   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
6683   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
6684   SVN_ERR(svn_fs_make_file(txn_root, "foo", pool));
6685 
6686   SVN_TEST_ASSERT_ERROR(svn_fs_merge(NULL, root1, "/", txn_root, "/", root0,
6687                                      "/", pool), SVN_ERR_FS_CONFLICT);
6688 
6689   return SVN_NO_ERROR;
6690 }
6691 
6692 static svn_error_t *
test_fsfs_config_opts(const svn_test_opts_t * opts,apr_pool_t * pool)6693 test_fsfs_config_opts(const svn_test_opts_t *opts,
6694                       apr_pool_t *pool)
6695 {
6696   apr_hash_t *fs_config;
6697   svn_fs_t *fs;
6698   const svn_fs_info_placeholder_t *fs_info;
6699   const svn_fs_fsfs_info_t *fsfs_info;
6700   const char *dir_name = "test-repo-fsfs-config-opts";
6701   const char *repo_name_default = "test-repo-fsfs-config-opts/default";
6702   const char *repo_name_custom = "test-repo-fsfs-config-opts/custom";
6703 
6704   /* Bail (with SKIP) on known-untestable scenarios */
6705   if (strcmp(opts->fs_type, SVN_FS_TYPE_FSFS) != 0)
6706     return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
6707                             "this will test FSFS repositories only");
6708 
6709   /* Remove the test directory from previous runs. */
6710   SVN_ERR(svn_io_remove_dir2(dir_name, TRUE, NULL, NULL, pool));
6711 
6712   /* Create the test directory and add it to the test cleanup list. */
6713   SVN_ERR(svn_io_dir_make(dir_name, APR_OS_DEFAULT, pool));
6714   svn_test_add_dir_cleanup(dir_name);
6715 
6716   /* Create an FSFS filesystem with default config.*/
6717   fs_config = apr_hash_make(pool);
6718   svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, SVN_FS_TYPE_FSFS);
6719   SVN_ERR(svn_fs_create(&fs, repo_name_default, fs_config, pool));
6720 
6721   /* Re-open FS to test the data on disk. */
6722   SVN_ERR(svn_fs_open2(&fs, repo_name_default, NULL, pool, pool));
6723 
6724   SVN_ERR(svn_fs_info(&fs_info, fs, pool, pool));
6725   SVN_TEST_STRING_ASSERT(fs_info->fs_type, SVN_FS_TYPE_FSFS);
6726   fsfs_info = (const void *) fs_info;
6727 
6728   /* Check FSFS specific info. Don't check the SHARD_SIZE, because it depends
6729    * on a compile-time constant and may be overridden. */
6730   SVN_TEST_ASSERT(fsfs_info->log_addressing);
6731   SVN_TEST_ASSERT(fsfs_info->min_unpacked_rev == 0);
6732 
6733   /* Create an FSFS filesystem with custom settings: disabled log-addressing
6734    * and custom shard size (123). */
6735   fs_config = apr_hash_make(pool);
6736   svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, SVN_FS_TYPE_FSFS);
6737   svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_LOG_ADDRESSING, "false");
6738   svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_SHARD_SIZE, "123");
6739   SVN_ERR(svn_fs_create(&fs, repo_name_custom, fs_config, pool));
6740 
6741   /* Re-open FS to test the data on disk. */
6742   SVN_ERR(svn_fs_open2(&fs, repo_name_custom, NULL, pool, pool));
6743 
6744   SVN_ERR(svn_fs_info(&fs_info, fs, pool, pool));
6745   SVN_TEST_STRING_ASSERT(fs_info->fs_type, SVN_FS_TYPE_FSFS);
6746   fsfs_info = (const void *) fs_info;
6747 
6748   /* Check FSFS specific info, including the SHARD_SIZE. */
6749   SVN_TEST_ASSERT(fsfs_info->log_addressing == FALSE);
6750   SVN_TEST_ASSERT(fsfs_info->shard_size == 123);
6751   SVN_TEST_ASSERT(fsfs_info->min_unpacked_rev == 0);
6752 
6753   return SVN_NO_ERROR;
6754 }
6755 
6756 static svn_error_t *
test_txn_pool_lifetime(const svn_test_opts_t * opts,apr_pool_t * pool)6757 test_txn_pool_lifetime(const svn_test_opts_t *opts,
6758                        apr_pool_t *pool)
6759 {
6760   /* Technically, the FS API makes no assumption on the lifetime of logically
6761    * dependent objects.  In particular, a txn root object may get destroyed
6762    * after the FS object that it has been built upon.  Actual data access is
6763    * implied to be invalid without a valid svn_fs_t.
6764    *
6765    * This test verifies that at least the destruction order of those two
6766    * objects is arbitrary.
6767    */
6768   svn_fs_t *fs;
6769   svn_fs_txn_t *txn;
6770   svn_fs_root_t *txn_root;
6771 
6772   /* We will allocate FS in FS_POOL.  Using a separate allocator makes
6773    * sure that we actually free the memory when destroying the pool.
6774    */
6775   apr_allocator_t *fs_allocator = svn_pool_create_allocator(FALSE);
6776   apr_pool_t *fs_pool = apr_allocator_owner_get(fs_allocator);
6777 
6778   /* Create a new repo. */
6779   SVN_ERR(svn_test__create_fs(&fs, "test-repo-pool-lifetime",
6780                               opts, fs_pool));
6781 
6782   /* Create a TXN_ROOT referencing FS. */
6783   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
6784   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
6785 
6786   /* Destroy FS.  Depending on the actual allocator implementation,
6787    * these memory pages becomes inaccessible. */
6788   svn_pool_destroy(fs_pool);
6789 
6790   /* Unclean implementations will try to access FS and may segfault here. */
6791   svn_fs_close_root(txn_root);
6792 
6793   return SVN_NO_ERROR;
6794 }
6795 
6796 static svn_error_t *
test_modify_txn_being_written(const svn_test_opts_t * opts,apr_pool_t * pool)6797 test_modify_txn_being_written(const svn_test_opts_t *opts,
6798                               apr_pool_t *pool)
6799 {
6800   /* FSFS has a limitation (and check) that only one file can be
6801    * modified in TXN at time: see r861812 and svn_fs_apply_text() docstring.
6802    * This is regression test for this behavior. */
6803   svn_fs_t *fs;
6804   svn_fs_txn_t *txn;
6805   const char *txn_name;
6806   svn_fs_root_t *txn_root;
6807   svn_stream_t *foo_contents;
6808   svn_stream_t *bar_contents;
6809 
6810   /* Bail (with success) on known-untestable scenarios */
6811   if (strcmp(opts->fs_type, SVN_FS_TYPE_BDB) == 0)
6812     return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
6813                             "this will not test BDB repositories");
6814 
6815   /* Create a new repo. */
6816   SVN_ERR(svn_test__create_fs(&fs, "test-repo-modify-txn-being-written",
6817                               opts, pool));
6818 
6819   /* Create a TXN_ROOT referencing FS. */
6820   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
6821   SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
6822   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
6823 
6824   /* Make file /foo and open for writing.*/
6825   SVN_ERR(svn_fs_make_file(txn_root, "/foo", pool));
6826   SVN_ERR(svn_fs_apply_text(&foo_contents, txn_root, "/foo", NULL, pool));
6827 
6828   /* Attempt to modify another file '/bar' -- FSFS doesn't allow this. */
6829   SVN_ERR(svn_fs_make_file(txn_root, "/bar", pool));
6830   SVN_TEST_ASSERT_ERROR(
6831       svn_fs_apply_text(&bar_contents, txn_root, "/bar", NULL, pool),
6832       SVN_ERR_FS_REP_BEING_WRITTEN);
6833 
6834   /* *Reopen TXN. */
6835   SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, pool));
6836   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
6837 
6838   /* Check that file '/bar' still cannot be modified */
6839   SVN_TEST_ASSERT_ERROR(
6840       svn_fs_apply_text(&bar_contents, txn_root, "/bar", NULL, pool),
6841       SVN_ERR_FS_REP_BEING_WRITTEN);
6842 
6843   /* Close file '/foo'. */
6844   SVN_ERR(svn_stream_close(foo_contents));
6845 
6846   /* Now file '/bar' can be modified. */
6847   SVN_ERR(svn_fs_apply_text(&bar_contents, txn_root, "/bar", NULL, pool));
6848 
6849   return SVN_NO_ERROR;
6850 }
6851 
6852 static svn_error_t *
test_prop_and_text_rep_sharing_collision(const svn_test_opts_t * opts,apr_pool_t * pool)6853 test_prop_and_text_rep_sharing_collision(const svn_test_opts_t *opts,
6854                                          apr_pool_t *pool)
6855 {
6856   /* Regression test for issue 4554: Wrong file length with PLAIN
6857    * representations in FSFS. */
6858   svn_fs_t *fs;
6859   svn_fs_txn_t *txn;
6860   svn_fs_root_t *txn_root;
6861   svn_fs_root_t *rev_root;
6862   svn_revnum_t new_rev;
6863   svn_filesize_t length;
6864   const char *testdir = "test-repo-prop-and-text-rep-sharing-collision";
6865 
6866   /* Create a new repo. */
6867   SVN_ERR(svn_test__create_fs(&fs, testdir, opts, pool));
6868 
6869   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
6870   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
6871   /* Set node property for the root. */
6872   SVN_ERR(svn_fs_change_node_prop(txn_root, "/", "prop",
6873                                   svn_string_create("value", pool),
6874                                   pool));
6875 
6876   /* Commit revision r1. */
6877   SVN_ERR(test_commit_txn(&new_rev, txn, NULL, pool));
6878 
6879   SVN_ERR(svn_fs_begin_txn(&txn, fs, 1, pool));
6880   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
6881 
6882   /* Create file with same contents as property representation. */
6883   SVN_ERR(svn_fs_make_file(txn_root, "/foo", pool));
6884   SVN_ERR(svn_test__set_file_contents(txn_root, "/foo",
6885                                       "K 4\n"
6886                                       "prop\n"
6887                                       "V 5\n"
6888                                       "value\n"
6889                                       "END\n", pool));
6890 
6891   /* Commit revision r2. */
6892   SVN_ERR(test_commit_txn(&new_rev, txn, NULL, pool));
6893 
6894   /* Check that FS reports correct length for the file (23). */
6895   SVN_ERR(svn_fs_revision_root(&rev_root, fs, 2, pool));
6896   SVN_ERR(svn_fs_file_length(&length, rev_root, "/foo", pool));
6897 
6898   SVN_TEST_ASSERT(length == 23);
6899   return SVN_NO_ERROR;
6900 }
6901 
6902 static svn_error_t *
test_internal_txn_props(const svn_test_opts_t * opts,apr_pool_t * pool)6903 test_internal_txn_props(const svn_test_opts_t *opts,
6904                         apr_pool_t *pool)
6905 {
6906   svn_fs_t *fs;
6907   svn_fs_txn_t *txn;
6908   svn_string_t *val;
6909   svn_prop_t prop;
6910   svn_prop_t internal_prop;
6911   apr_array_header_t *props;
6912   apr_hash_t *proplist;
6913   svn_error_t *err;
6914 
6915   SVN_ERR(svn_test__create_fs(&fs, "test-repo-internal-txn-props",
6916                               opts, pool));
6917   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0,
6918                             SVN_FS_TXN_CHECK_LOCKS |
6919                             SVN_FS_TXN_CHECK_OOD |
6920                             SVN_FS_TXN_CLIENT_DATE, pool));
6921 
6922   /* Ensure that we cannot read internal transaction properties. */
6923   SVN_ERR(svn_fs_txn_prop(&val, txn, SVN_FS__PROP_TXN_CHECK_LOCKS, pool));
6924   SVN_TEST_ASSERT(!val);
6925   SVN_ERR(svn_fs_txn_prop(&val, txn, SVN_FS__PROP_TXN_CHECK_OOD, pool));
6926   SVN_TEST_ASSERT(!val);
6927   SVN_ERR(svn_fs_txn_prop(&val, txn, SVN_FS__PROP_TXN_CLIENT_DATE, pool));
6928   SVN_TEST_ASSERT(!val);
6929 
6930   SVN_ERR(svn_fs_txn_proplist(&proplist, txn, pool));
6931   SVN_TEST_ASSERT(apr_hash_count(proplist) == 1);
6932   val = svn_hash_gets(proplist, SVN_PROP_REVISION_DATE);
6933   SVN_TEST_ASSERT(val);
6934 
6935   /* We also cannot change or discard them. */
6936   val = svn_string_create("Ooops!", pool);
6937 
6938   err = svn_fs_change_txn_prop(txn, SVN_FS__PROP_TXN_CHECK_LOCKS, val, pool);
6939   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
6940   err = svn_fs_change_txn_prop(txn, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, pool);
6941   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
6942   err = svn_fs_change_txn_prop(txn, SVN_FS__PROP_TXN_CHECK_OOD, val, pool);
6943   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
6944   err = svn_fs_change_txn_prop(txn, SVN_FS__PROP_TXN_CHECK_OOD, NULL, pool);
6945   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
6946   err = svn_fs_change_txn_prop(txn, SVN_FS__PROP_TXN_CLIENT_DATE, val, pool);
6947   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
6948   err = svn_fs_change_txn_prop(txn, SVN_FS__PROP_TXN_CLIENT_DATE, NULL, pool);
6949   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
6950 
6951   prop.name = "foo";
6952   prop.value = svn_string_create("bar", pool);
6953   internal_prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
6954   internal_prop.value = svn_string_create("Ooops!", pool);
6955 
6956   props = apr_array_make(pool, 2, sizeof(svn_prop_t));
6957   APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6958   APR_ARRAY_PUSH(props, svn_prop_t) = internal_prop;
6959 
6960   err = svn_fs_change_txn_props(txn, props, pool);
6961   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
6962 
6963   return SVN_NO_ERROR;
6964 }
6965 
6966 /* A freeze function that expects an 'svn_error_t *' baton, and returns it. */
6967 /* This function implements svn_fs_freeze_func_t. */
6968 static svn_error_t *
freeze_func(void * baton,apr_pool_t * pool)6969 freeze_func(void *baton, apr_pool_t *pool)
6970 {
6971   return baton;
6972 }
6973 
6974 static svn_error_t *
freeze_and_commit(const svn_test_opts_t * opts,apr_pool_t * pool)6975 freeze_and_commit(const svn_test_opts_t *opts,
6976                   apr_pool_t *pool)
6977 {
6978   svn_fs_t *fs;
6979   svn_fs_txn_t *txn;
6980   svn_fs_root_t *txn_root;
6981   svn_revnum_t new_rev = 0;
6982   apr_pool_t *subpool = svn_pool_create(pool);
6983   const char *repo_name = "test-repo-freeze-and-commit";
6984 
6985   if (!strcmp(opts->fs_type, "bdb"))
6986     return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
6987                             "this will not test BDB repositories");
6988 
6989   SVN_ERR(svn_test__create_fs(&fs, repo_name, opts, subpool));
6990 
6991   /* This test used to FAIL with an SQLite error since svn_fs_freeze()
6992    * wouldn't unlock rep-cache.db.  Therefore, part of the role of creating
6993    * the Greek tree is to create a rep-cache.db, in order to test that
6994    * svn_fs_freeze() unlocks it. */
6995 
6996   /* r1: Commit the Greek tree. */
6997   SVN_ERR(svn_fs_begin_txn(&txn, fs, new_rev, subpool));
6998   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
6999   SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
7000   SVN_ERR(test_commit_txn(&new_rev, txn, NULL, subpool));
7001 
7002   /* Freeze and unfreeze. */
7003   SVN_ERR(svn_fs_freeze(fs, freeze_func, SVN_NO_ERROR, pool));
7004 
7005   /* Freeze again, but have freeze_func fail. */
7006     {
7007       svn_error_t *err = svn_error_create(APR_EGENERAL, NULL, NULL);
7008       SVN_TEST_ASSERT_ERROR(svn_fs_freeze(fs, freeze_func, err, pool),
7009                             err->apr_err);
7010     }
7011 
7012   /* Make some commit using same FS instance. */
7013   SVN_ERR(svn_fs_begin_txn(&txn, fs, new_rev, pool));
7014   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
7015   SVN_ERR(svn_fs_change_node_prop(txn_root, "", "temperature",
7016                                   svn_string_create("310.05", pool),
7017                                   pool));
7018   SVN_ERR(test_commit_txn(&new_rev, txn, NULL, pool));
7019 
7020   /* Re-open FS and make another commit. */
7021   SVN_ERR(svn_fs_open(&fs, repo_name, NULL, subpool));
7022   SVN_ERR(svn_fs_begin_txn(&txn, fs, new_rev, pool));
7023   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
7024   SVN_ERR(svn_fs_change_node_prop(txn_root, "/", "temperature",
7025                                   svn_string_create("451", pool),
7026                                   pool));
7027   SVN_ERR(test_commit_txn(&new_rev, txn, NULL, pool));
7028 
7029   return SVN_NO_ERROR;
7030 }
7031 
7032 /* Number of changes in a revision.
7033  * Should be > 100 to span multiple blocks. */
7034 #define CHANGES_COUNT 1017
7035 
7036 /* Check that REVISION in FS reports the expected changes. */
7037 static svn_error_t *
verify_added_files_list(svn_fs_t * fs,svn_revnum_t revision,apr_pool_t * scratch_pool)7038 verify_added_files_list(svn_fs_t *fs,
7039                         svn_revnum_t revision,
7040                         apr_pool_t *scratch_pool)
7041 {
7042   int i;
7043   svn_fs_root_t *root;
7044   apr_hash_t *changed_paths;
7045   svn_fs_path_change_iterator_t *iterator;
7046   svn_fs_path_change3_t *change;
7047   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
7048 
7049   /* Collect changes and test that no path gets reported twice. */
7050   SVN_ERR(svn_fs_revision_root(&root, fs, revision, scratch_pool));
7051   SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool));
7052 
7053   changed_paths = apr_hash_make(scratch_pool);
7054   SVN_ERR(svn_fs_path_change_get(&change, iterator));
7055   while (change)
7056     {
7057       const char *path = apr_pstrmemdup(scratch_pool, change->path.data,
7058                                         change->path.len);
7059       SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_add);
7060       SVN_TEST_ASSERT(!apr_hash_get(changed_paths, path, change->path.len));
7061 
7062       apr_hash_set(changed_paths, path, change->path.len, path);
7063       SVN_ERR(svn_fs_path_change_get(&change, iterator));
7064     }
7065 
7066   /* Verify that we've got exactly all paths that we added. */
7067   SVN_TEST_ASSERT(CHANGES_COUNT == apr_hash_count(changed_paths));
7068   for (i = 0; i < CHANGES_COUNT; ++i)
7069     {
7070       const char *file_name;
7071       svn_pool_clear(iterpool);
7072 
7073       file_name = apr_psprintf(iterpool, "/file-%d", i);
7074       SVN_TEST_ASSERT(svn_hash_gets(changed_paths, file_name));
7075     }
7076 
7077   return SVN_NO_ERROR;
7078 }
7079 
7080 static svn_error_t *
test_large_changed_paths_list(const svn_test_opts_t * opts,apr_pool_t * pool)7081 test_large_changed_paths_list(const svn_test_opts_t *opts,
7082                               apr_pool_t *pool)
7083 {
7084   svn_fs_t *fs;
7085   svn_fs_txn_t *txn;
7086   svn_fs_root_t *txn_root;
7087   int i;
7088   svn_revnum_t rev = 0;
7089   apr_pool_t *iterpool = svn_pool_create(pool);
7090   const char *repo_name = "test-repo-changed-paths-list";
7091 
7092   SVN_ERR(svn_test__create_fs(&fs, repo_name, opts, pool));
7093 
7094   /* r1: Add many empty files - just to amass a long list of changes. */
7095   SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, pool));
7096   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
7097 
7098   for (i = 0; i < CHANGES_COUNT; ++i)
7099     {
7100       const char *file_name;
7101       svn_pool_clear(iterpool);
7102 
7103       file_name = apr_psprintf(iterpool, "/file-%d", i);
7104       SVN_ERR(svn_fs_make_file(txn_root, file_name, iterpool));
7105     }
7106 
7107   SVN_ERR(test_commit_txn(&rev, txn, NULL, pool));
7108 
7109   /* Now, read the change list.
7110    * Do it twice to cover cached data as well. */
7111   svn_pool_clear(iterpool);
7112   SVN_ERR(verify_added_files_list(fs, rev, iterpool));
7113   svn_pool_clear(iterpool);
7114   SVN_ERR(verify_added_files_list(fs, rev, iterpool));
7115   svn_pool_destroy(iterpool);
7116 
7117   return SVN_NO_ERROR;
7118 }
7119 
7120 #undef CHANGES_COUNT
7121 
7122 static svn_error_t *
commit_with_locked_rep_cache(const svn_test_opts_t * opts,apr_pool_t * pool)7123 commit_with_locked_rep_cache(const svn_test_opts_t *opts,
7124                              apr_pool_t *pool)
7125 {
7126   svn_fs_t *fs;
7127   svn_fs_txn_t *txn;
7128   svn_fs_root_t *txn_root;
7129   svn_revnum_t new_rev;
7130   svn_sqlite__db_t *sdb;
7131   svn_error_t *err;
7132   const char *fs_path;
7133   const char *statements[] = { "SELECT MAX(revision) FROM rep_cache", NULL };
7134 
7135   if (strcmp(opts->fs_type, SVN_FS_TYPE_BDB) == 0)
7136     return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
7137                             "this will not test BDB repositories");
7138 
7139   if (opts->server_minor_version && (opts->server_minor_version < 6))
7140     return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
7141                             "pre-1.6 SVN doesn't support FSFS rep-sharing");
7142 
7143   fs_path = "test-repo-commit-with-locked-rep-cache";
7144   SVN_ERR(svn_test__create_fs(&fs, fs_path, opts, pool));
7145 
7146   /* r1: Add a file. */
7147   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, 0, pool));
7148   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
7149   SVN_ERR(svn_fs_make_file(txn_root, "/foo", pool));
7150   SVN_ERR(svn_test__set_file_contents(txn_root, "/foo", "a", pool));
7151   SVN_ERR(test_commit_txn(&new_rev, txn, NULL, pool));
7152   SVN_TEST_INT_ASSERT(new_rev, 1);
7153 
7154   /* Begin a new transaction based on r1. */
7155   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 1, 0, pool));
7156   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
7157   SVN_ERR(svn_test__set_file_contents(txn_root, "/foo", "b", pool));
7158 
7159   /* Obtain a shared lock on the rep-cache.db by starting a new read
7160    * transaction. */
7161   SVN_ERR(svn_sqlite__open(&sdb,
7162                            svn_dirent_join(fs_path, "rep-cache.db", pool),
7163                            svn_sqlite__mode_readonly, statements, 0, NULL,
7164                            0, pool, pool));
7165   SVN_ERR(svn_sqlite__begin_transaction(sdb));
7166   SVN_ERR(svn_sqlite__exec_statements(sdb, 0));
7167 
7168   /* Attempt to commit fs transaction.  This should result in a commit
7169    * post-processing error due to us still holding the shared lock on the
7170    * rep-cache.db. */
7171   err = svn_fs_commit_txn(NULL, &new_rev, txn, pool);
7172   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_SQLITE_BUSY);
7173   SVN_TEST_INT_ASSERT(new_rev, 2);
7174 
7175   /* Release the shared lock. */
7176   SVN_ERR(svn_sqlite__finish_transaction(sdb, SVN_NO_ERROR));
7177   SVN_ERR(svn_sqlite__close(sdb));
7178 
7179   /* Try an operation that reads from rep-cache.db.
7180    *
7181    * XFAIL: Around r1740802, this call was producing an error due to the
7182    * svn_fs_t keeping an unusable db connection (and associated file
7183    * locks) within it.
7184    */
7185   SVN_ERR(svn_fs_verify(fs_path, NULL, 0, SVN_INVALID_REVNUM, NULL, NULL,
7186                         NULL, NULL, pool));
7187 
7188   return SVN_NO_ERROR;
7189 }
7190 
7191 
7192 static svn_error_t *
test_cache_clear_during_stream(const svn_test_opts_t * opts,apr_pool_t * pool)7193 test_cache_clear_during_stream(const svn_test_opts_t *opts,
7194                                apr_pool_t *pool)
7195 {
7196   svn_fs_t *fs;
7197   svn_fs_txn_t *txn;
7198   svn_fs_root_t *txn_root, *rev_root;
7199   svn_revnum_t new_rev;
7200   const char *fs_path;
7201   apr_pool_t *iterpool = svn_pool_create(pool);
7202   apr_pool_t *subpool = svn_pool_create(pool);
7203   svn_txdelta_window_handler_t consumer_func;
7204   void *consumer_baton;
7205   int i;
7206   svn_stream_t *stream;
7207   svn_stringbuf_t *buf;
7208 
7209 
7210   fs_path = "test-repo-cache_clear_during_stream";
7211   SVN_ERR(svn_test__create_fs(&fs, fs_path, opts, pool));
7212 
7213   /* r1: Add a file. */
7214   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, 0, pool));
7215   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
7216   SVN_ERR(svn_fs_make_file(txn_root, "/foo", pool));
7217 
7218   /* Make the file large enough to span multiple txdelta windows.
7219    * Just to be sure, make it not too uniform to keep self-txdelta at bay. */
7220   SVN_ERR(svn_fs_apply_textdelta(&consumer_func, &consumer_baton,
7221                                  txn_root, "/foo", NULL, NULL, subpool));
7222   stream = svn_txdelta_target_push(consumer_func, consumer_baton,
7223                                    svn_stream_empty(subpool), subpool);
7224   for (i = 0; i < 10000; ++ i)
7225     {
7226       svn_string_t *text;
7227 
7228       svn_pool_clear(iterpool);
7229       text = svn_string_createf(iterpool, "some dummy text - %d\n", i);
7230       SVN_ERR(svn_stream_write(stream, text->data, &text->len));
7231     }
7232 
7233   SVN_ERR(svn_stream_close(stream));
7234   svn_pool_destroy(subpool);
7235 
7236   SVN_ERR(test_commit_txn(&new_rev, txn, NULL, pool));
7237   SVN_TEST_INT_ASSERT(new_rev, 1);
7238 
7239   /* Read the file once to populate the fulltext cache. */
7240   SVN_ERR(svn_fs_revision_root(&rev_root, fs, 1, pool));
7241   SVN_ERR(svn_fs_file_contents(&stream, rev_root, "/foo", pool));
7242   SVN_ERR(svn_test__stream_to_string(&buf, stream, pool));
7243 
7244   /* Start reading it again from cache, clear the cache and continue.
7245    * Make sure we read more than one txdelta window before clearing
7246    * the cache.  That gives the FS backend a chance to skip windows
7247    * when continuing the read from disk. */
7248   SVN_ERR(svn_fs_file_contents(&stream, rev_root, "/foo", pool));
7249   buf->len = 2 * SVN_STREAM_CHUNK_SIZE;
7250   SVN_ERR(svn_stream_read_full(stream, buf->data, &buf->len));
7251   SVN_ERR(svn_cache__membuffer_clear(svn_cache__get_global_membuffer_cache()));
7252   SVN_ERR(svn_test__stream_to_string(&buf, stream, pool));
7253 
7254   svn_pool_destroy(iterpool);
7255 
7256   return SVN_NO_ERROR;
7257 }
7258 
7259 static svn_error_t *
test_rep_sharing_strict_content_check(const svn_test_opts_t * opts,apr_pool_t * pool)7260 test_rep_sharing_strict_content_check(const svn_test_opts_t *opts,
7261                                       apr_pool_t *pool)
7262 {
7263   svn_fs_t *fs;
7264   svn_fs_txn_t *txn;
7265   svn_fs_root_t *txn_root;
7266   svn_revnum_t new_rev;
7267   const char *fs_path, *fs_path2;
7268   apr_pool_t *subpool = svn_pool_create(pool);
7269   svn_error_t *err;
7270 
7271   /* Bail (with success) on known-untestable scenarios */
7272   if (strcmp(opts->fs_type, SVN_FS_TYPE_BDB) == 0)
7273     return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
7274                             "BDB repositories don't support rep-sharing");
7275 
7276   /* Create 2 repos with same structure & size but different contents */
7277   fs_path = "test-rep-sharing-strict-content-check1";
7278   fs_path2 = "test-rep-sharing-strict-content-check2";
7279 
7280   SVN_ERR(svn_test__create_fs(&fs, fs_path, opts, subpool));
7281 
7282   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, 0, subpool));
7283   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
7284   SVN_ERR(svn_fs_make_file(txn_root, "/foo", subpool));
7285   SVN_ERR(svn_test__set_file_contents(txn_root, "foo", "quite bad", subpool));
7286   SVN_ERR(test_commit_txn(&new_rev, txn, NULL, subpool));
7287   SVN_TEST_INT_ASSERT(new_rev, 1);
7288 
7289   SVN_ERR(svn_test__create_fs(&fs, fs_path2, opts, subpool));
7290 
7291   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, 0, subpool));
7292   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
7293   SVN_ERR(svn_fs_make_file(txn_root, "foo", subpool));
7294   SVN_ERR(svn_test__set_file_contents(txn_root, "foo", "very good", subpool));
7295   SVN_ERR(test_commit_txn(&new_rev, txn, NULL, subpool));
7296   SVN_TEST_INT_ASSERT(new_rev, 1);
7297 
7298   /* Close both repositories. */
7299   svn_pool_clear(subpool);
7300 
7301   /* Doctor the first repo such that it uses the wrong rep-cache. */
7302   SVN_ERR(svn_io_copy_file(svn_relpath_join(fs_path2, "rep-cache.db", pool),
7303                            svn_relpath_join(fs_path, "rep-cache.db", pool),
7304                            FALSE, pool));
7305 
7306   /* Changing the file contents such that rep-sharing would kick in if
7307      the file contents was not properly compared. */
7308   SVN_ERR(svn_fs_open2(&fs, fs_path, NULL, subpool, subpool));
7309 
7310   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 1, 0, subpool));
7311   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
7312   err = svn_test__set_file_contents(txn_root, "foo", "very good", subpool);
7313   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_FS_AMBIGUOUS_CHECKSUM_REP);
7314 
7315   svn_pool_destroy(subpool);
7316 
7317   return SVN_NO_ERROR;
7318 }
7319 
7320 static svn_error_t *
closest_copy_test_svn_4677(const svn_test_opts_t * opts,apr_pool_t * pool)7321 closest_copy_test_svn_4677(const svn_test_opts_t *opts,
7322                            apr_pool_t *pool)
7323 {
7324   svn_fs_t *fs;
7325   svn_fs_txn_t *txn;
7326   svn_fs_root_t *txn_root, *rev_root, *croot;
7327   svn_revnum_t after_rev;
7328   const char *cpath;
7329   apr_pool_t *spool = svn_pool_create(pool);
7330 
7331   /* Prepare a filesystem. */
7332   SVN_ERR(svn_test__create_fs(&fs, "test-repo-svn-4677",
7333                               opts, pool));
7334 
7335   /* In first txn, create file A/foo. */
7336   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, spool));
7337   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
7338   SVN_ERR(svn_fs_make_dir(txn_root, "A", spool));
7339   SVN_ERR(svn_fs_make_file(txn_root, "A/foo", spool));
7340   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, spool));
7341   svn_pool_clear(spool);
7342   SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, spool));
7343 
7344   /* Move A to B, and commit. */
7345   SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, spool));
7346   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
7347   SVN_ERR(svn_fs_copy(rev_root, "A", txn_root, "B", spool));
7348   SVN_ERR(svn_fs_delete(txn_root, "A", spool));
7349   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, spool));
7350   svn_pool_clear(spool);
7351   SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, spool));
7352 
7353   /* Replace file B/foo with directory B/foo, add B/foo/bar, and commit. */
7354   SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, spool));
7355   SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
7356   SVN_ERR(svn_fs_delete(txn_root, "B/foo", spool));
7357   SVN_ERR(svn_fs_make_dir(txn_root, "B/foo", spool));
7358   SVN_ERR(svn_fs_make_file(txn_root, "B/foo/bar", spool));
7359   SVN_ERR(test_commit_txn(&after_rev, txn, NULL, spool));
7360   svn_pool_clear(spool);
7361   SVN_ERR(svn_fs_revision_root(&rev_root, fs, after_rev, spool));
7362 
7363   /* B/foo/bar has been copied.
7364      Issue 4677 was caused by returning an error in this situation. */
7365   SVN_ERR(svn_fs_closest_copy(&croot, &cpath, rev_root, "B/foo/bar", spool));
7366   SVN_TEST_ASSERT(cpath == NULL);
7367   SVN_TEST_ASSERT(croot == NULL);
7368 
7369   return SVN_NO_ERROR;
7370 }
7371 
7372 static svn_error_t *
test_closest_copy_file_replaced_with_dir(const svn_test_opts_t * opts,apr_pool_t * pool)7373 test_closest_copy_file_replaced_with_dir(const svn_test_opts_t *opts,
7374                                          apr_pool_t *pool)
7375 {
7376   svn_fs_t *fs;
7377   svn_fs_txn_t *txn;
7378   svn_fs_root_t *txn_root;
7379   svn_fs_root_t *rev_root;
7380   svn_revnum_t youngest_rev;
7381   svn_fs_root_t *copy_root;
7382   const char *copy_path;
7383 
7384   /* Prepare a filesystem. */
7385   SVN_ERR(svn_test__create_fs(&fs, "test-closest-copy-file-replaced-with-dir",
7386                               opts, pool));
7387 
7388   youngest_rev = 0;
7389 
7390   /* Modeled after the case described in the thread:
7391        "[PATCH] A test for "Can't get entries" error"
7392        https://lists.apache.org/thread.html/693a95b0da834387e78a7f08df2392b634397d32f37428c81c02f8c5@%3Cdev.subversion.apache.org%3E
7393   */
7394   /* r1: Add a directory with a file. */
7395   SVN_ERR(svn_fs_begin_txn2(&txn, fs, youngest_rev, 0, pool));
7396   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
7397   SVN_ERR(svn_fs_make_dir(txn_root, "/A", pool));
7398   SVN_ERR(svn_fs_make_file(txn_root, "/A/mu", pool));
7399   SVN_ERR(test_commit_txn(&youngest_rev, txn, NULL, pool));
7400   SVN_TEST_INT_ASSERT(youngest_rev, 1);
7401 
7402   /* r2: Copy the directory. */
7403   SVN_ERR(svn_fs_begin_txn2(&txn, fs, youngest_rev, 0, pool));
7404   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
7405   SVN_ERR(svn_fs_revision_root(&rev_root, fs, 1, pool));
7406   SVN_ERR(svn_fs_copy(rev_root, "/A", txn_root, "/B", pool));
7407   SVN_ERR(test_commit_txn(&youngest_rev, txn, NULL, pool));
7408   SVN_TEST_INT_ASSERT(youngest_rev, 2);
7409 
7410   /* r3: Delete the file. */
7411   SVN_ERR(svn_fs_begin_txn2(&txn, fs, youngest_rev, 0, pool));
7412   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
7413   SVN_ERR(svn_fs_delete(txn_root, "/B/mu", pool));
7414   SVN_ERR(test_commit_txn(&youngest_rev, txn, NULL, pool));
7415   SVN_TEST_INT_ASSERT(youngest_rev, 3);
7416 
7417   /* r4: Replace the file with a new directory containing a file. */
7418   SVN_ERR(svn_fs_begin_txn2(&txn, fs, youngest_rev, 0, pool));
7419   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
7420   SVN_ERR(svn_fs_make_dir(txn_root, "/B/mu", pool));
7421   SVN_ERR(svn_fs_make_file(txn_root, "/B/mu/iota", pool));
7422   SVN_ERR(test_commit_txn(&youngest_rev, txn, NULL, pool));
7423   SVN_TEST_INT_ASSERT(youngest_rev, 4);
7424 
7425   /* Test a couple of svn_fs_closest_copy() calls; the second call used
7426      to fail with an unexpected SVN_ERR_FS_NOT_DIRECTORY error. */
7427 
7428   SVN_ERR(svn_fs_revision_root(&rev_root, fs, 2, pool));
7429   SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, rev_root, "/B/mu", pool));
7430 
7431   SVN_TEST_ASSERT(copy_root != NULL);
7432   SVN_TEST_INT_ASSERT(svn_fs_revision_root_revision(copy_root), 2);
7433   SVN_TEST_STRING_ASSERT(copy_path, "/B");
7434 
7435   SVN_ERR(svn_fs_revision_root(&rev_root, fs, 4, pool));
7436   SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, rev_root, "/B/mu/iota", pool));
7437 
7438   SVN_TEST_ASSERT(copy_root == NULL);
7439   SVN_TEST_ASSERT(copy_path == NULL);
7440 
7441   return SVN_NO_ERROR;
7442 }
7443 
7444 static svn_error_t *
test_unrecognized_ioctl(const svn_test_opts_t * opts,apr_pool_t * pool)7445 test_unrecognized_ioctl(const svn_test_opts_t *opts,
7446                         apr_pool_t *pool)
7447 {
7448   svn_fs_t *fs;
7449   svn_error_t *err;
7450   svn_fs_ioctl_code_t code = {0};
7451 
7452   SVN_ERR(svn_test__create_fs(&fs, "test-unrecognized-ioctl", opts, pool));
7453 
7454   code.fs_type = "NON-EXISTING";
7455   code.code = 98765;
7456   err = svn_fs_ioctl(fs, code, NULL, NULL, NULL, NULL, pool, pool);
7457   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_FS_UNRECOGNIZED_IOCTL_CODE);
7458 
7459   code.fs_type = "NON-EXISTING";
7460   code.code = 98765;
7461   err = svn_fs_ioctl(NULL, code, NULL, NULL, NULL, NULL, pool, pool);
7462   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_FS_UNKNOWN_FS_TYPE);
7463 
7464   code.fs_type = opts->fs_type;
7465   code.code = 98765;
7466   err = svn_fs_ioctl(NULL, code, NULL, NULL, NULL, NULL, pool, pool);
7467   SVN_TEST_ASSERT_ERROR(err, SVN_ERR_FS_UNRECOGNIZED_IOCTL_CODE);
7468 
7469   return SVN_NO_ERROR;
7470 }
7471 
7472 /* ------------------------------------------------------------------------ */
7473 
7474 /* The test table.  */
7475 
7476 static int max_threads = 8;
7477 
7478 static struct svn_test_descriptor_t test_funcs[] =
7479   {
7480     SVN_TEST_NULL,
7481     SVN_TEST_OPTS_PASS(trivial_transaction,
7482                        "begin a txn, check its name, then close it"),
7483     SVN_TEST_OPTS_PASS(reopen_trivial_transaction,
7484                        "open an existing transaction by name"),
7485     SVN_TEST_OPTS_PASS(create_file_transaction,
7486                        "begin a txn, get the txn root, and add a file"),
7487     SVN_TEST_OPTS_PASS(verify_txn_list,
7488                        "create 2 txns, list them, and verify the list"),
7489     SVN_TEST_OPTS_PASS(txn_names_are_not_reused,
7490                        "check that transaction names are not reused"),
7491     SVN_TEST_OPTS_PASS(write_and_read_file,
7492                        "write and read a file's contents"),
7493     SVN_TEST_OPTS_PASS(almostmedium_file_integrity,
7494                        "create and modify almostmedium file"),
7495     SVN_TEST_OPTS_PASS(medium_file_integrity,
7496                        "create and modify medium file"),
7497     SVN_TEST_OPTS_PASS(large_file_integrity,
7498                        "create and modify large file"),
7499     SVN_TEST_OPTS_PASS(create_mini_tree_transaction,
7500                        "test basic file and subdirectory creation"),
7501     SVN_TEST_OPTS_PASS(create_greek_tree_transaction,
7502                        "make The Official Subversion Test Tree"),
7503     SVN_TEST_OPTS_PASS(list_directory,
7504                        "fill a directory, then list it"),
7505     SVN_TEST_OPTS_PASS(revision_props,
7506                        "set and get some revision properties"),
7507     SVN_TEST_OPTS_PASS(transaction_props,
7508                        "set/get txn props, commit, validate new rev props"),
7509     SVN_TEST_OPTS_PASS(node_props,
7510                        "set and get some node properties"),
7511     SVN_TEST_OPTS_PASS(delete_mutables,
7512                        "delete mutable nodes from directories"),
7513     SVN_TEST_OPTS_PASS(delete,
7514                        "delete nodes tree"),
7515     SVN_TEST_OPTS_PASS(fetch_youngest_rev,
7516                        "fetch the youngest revision from a filesystem"),
7517     SVN_TEST_OPTS_PASS(basic_commit,
7518                        "basic commit"),
7519     SVN_TEST_OPTS_PASS(test_tree_node_validation,
7520                        "testing tree validation helper"),
7521     SVN_TEST_OPTS_PASS(merging_commit, "merging commit"),
7522     SVN_TEST_OPTS_PASS(copy_test,
7523                        "copying and tracking copy history"),
7524     SVN_TEST_OPTS_PASS(commit_date,
7525                        "commit datestamps"),
7526     SVN_TEST_OPTS_PASS(check_old_revisions,
7527                        "check old revisions"),
7528     SVN_TEST_OPTS_PASS(check_all_revisions,
7529                        "after each commit, check all revisions"),
7530     SVN_TEST_OPTS_PASS(check_root_revision,
7531                        "ensure accurate storage of root node"),
7532     SVN_TEST_OPTS_PASS(test_node_created_rev,
7533                        "svn_fs_node_created_rev test"),
7534     SVN_TEST_OPTS_PASS(check_related,
7535                        "test svn_fs_check_related"),
7536     SVN_TEST_OPTS_PASS(branch_test,
7537                        "test complex copies (branches)"),
7538     SVN_TEST_OPTS_PASS(verify_checksum,
7539                        "test checksums"),
7540     SVN_TEST_OPTS_PASS(closest_copy_test,
7541                        "calculating closest history-affecting copies"),
7542     SVN_TEST_OPTS_PASS(root_revisions,
7543                        "svn_fs_root_t (base) revisions"),
7544     SVN_TEST_OPTS_PASS(unordered_txn_dirprops,
7545                        "test dir prop preservation in unordered txns"),
7546     SVN_TEST_OPTS_PASS(set_uuid,
7547                        "test svn_fs_set_uuid"),
7548     SVN_TEST_OPTS_PASS(node_origin_rev,
7549                        "test svn_fs_node_origin_rev"),
7550     SVN_TEST_OPTS_PASS(small_file_integrity,
7551                        "create and modify small file"),
7552     SVN_TEST_OPTS_PASS(node_history,
7553                        "test svn_fs_node_history"),
7554     SVN_TEST_OPTS_PASS(delete_fs,
7555                        "test svn_fs_delete_fs"),
7556     SVN_TEST_OPTS_PASS(filename_trailing_newline,
7557                        "filenames with trailing \\n might be rejected"),
7558     SVN_TEST_OPTS_PASS(test_fs_info_format,
7559                        "test svn_fs_info_format"),
7560     SVN_TEST_OPTS_PASS(commit_timestamp,
7561                        "commit timestamp"),
7562     SVN_TEST_OPTS_PASS(test_compat_version,
7563                        "test svn_fs__compatible_version"),
7564     SVN_TEST_OPTS_PASS(dir_prop_merge,
7565                        "test merge directory properties"),
7566     SVN_TEST_OPTS_PASS(upgrade_while_committing,
7567                        "upgrade while committing"),
7568     SVN_TEST_OPTS_PASS(test_paths_changed,
7569                        "test svn_fs_paths_changed"),
7570     SVN_TEST_OPTS_PASS(test_delete_replaced_paths_changed,
7571                        "test deletion after replace in changed paths list"),
7572     SVN_TEST_OPTS_PASS(purge_txn_test,
7573                        "test purging transactions"),
7574     SVN_TEST_OPTS_PASS(compare_contents,
7575                        "compare contents of different nodes"),
7576     SVN_TEST_OPTS_PASS(test_path_change_create,
7577                        "test svn_fs_path_change2_create"),
7578     SVN_TEST_OPTS_PASS(test_node_created_info,
7579                        "test FS 'node created' info"),
7580     SVN_TEST_OPTS_PASS(test_print_modules,
7581                        "test FS module listing"),
7582     SVN_TEST_OPTS_PASS(test_zero_copy_processsing,
7583                        "test zero copy file processing"),
7584     SVN_TEST_OPTS_PASS(test_dir_optimal_order,
7585                        "test svn_fs_dir_optimal_order"),
7586     SVN_TEST_OPTS_PASS(test_config_files,
7587                        "get configuration files"),
7588     SVN_TEST_OPTS_PASS(test_delta_file_stream,
7589                        "get a delta stream on a file"),
7590     SVN_TEST_OPTS_PASS(test_fs_merge,
7591                        "get merging txns with newer revisions"),
7592     SVN_TEST_OPTS_PASS(test_fsfs_config_opts,
7593                        "test creating FSFS repository with different opts"),
7594     SVN_TEST_OPTS_PASS(test_txn_pool_lifetime,
7595                        "test pool lifetime dependencies with txn roots"),
7596     SVN_TEST_OPTS_PASS(test_modify_txn_being_written,
7597                        "test modify txn being written"),
7598     SVN_TEST_OPTS_PASS(test_prop_and_text_rep_sharing_collision,
7599                        "test property and text rep-sharing collision"),
7600     SVN_TEST_OPTS_PASS(test_internal_txn_props,
7601                        "test setting and getting internal txn props"),
7602     SVN_TEST_OPTS_PASS(check_txn_related,
7603                        "test svn_fs_check_related for transactions"),
7604     SVN_TEST_OPTS_PASS(freeze_and_commit,
7605                        "freeze and commit"),
7606     SVN_TEST_OPTS_PASS(test_large_changed_paths_list,
7607                        "test reading a large changed paths list"),
7608     SVN_TEST_OPTS_PASS(commit_with_locked_rep_cache,
7609                        "test commit with locked rep-cache"),
7610     SVN_TEST_OPTS_PASS(test_cache_clear_during_stream,
7611                        "test clearing the cache while streaming a rep"),
7612     SVN_TEST_OPTS_PASS(test_rep_sharing_strict_content_check,
7613                        "test rep-sharing on content rather than SHA1"),
7614     SVN_TEST_OPTS_PASS(closest_copy_test_svn_4677,
7615                        "test issue SVN-4677 regression"),
7616     SVN_TEST_OPTS_PASS(test_closest_copy_file_replaced_with_dir,
7617                        "svn_fs_closest_copy after replacing file with dir"),
7618     SVN_TEST_OPTS_PASS(test_unrecognized_ioctl,
7619                        "test svn_fs_ioctl with unrecognized code"),
7620     SVN_TEST_NULL
7621   };
7622 
7623 SVN_TEST_MAIN
7624