monotone

monotone Mtn Source Tree

Root/paths.cc

1// copyright (C) 2005 nathaniel smith <njs@pobox.com>
2// all rights reserved.
3// licensed to the public under the terms of the GNU GPL (>= 2)
4// see the file COPYING for details
5
6#include <iostream>
7#include <string>
8
9#include <boost/filesystem/path.hpp>
10#include <boost/filesystem/operations.hpp>
11#include <boost/filesystem/convenience.hpp>
12
13#include "constants.hh"
14#include "paths.hh"
15#include "platform.hh"
16#include "sanity.hh"
17#include "interner.hh"
18#include "transforms.hh"
19
20// some structure to ensure we aren't doing anything broken when resolving
21// filenames. the idea is to make sure
22// -- we don't depend on the existence of something before it has been set
23// -- we don't re-set something that has already been used
24// -- sometimes, we use the _non_-existence of something, so we shouldn't
25// set anything whose un-setted-ness has already been used
26template <typename T>
27struct access_tracker
28{
29 void set(T const & val, bool may_be_initialized)
30 {
31 I(may_be_initialized || !initialized);
32 I(!very_uninitialized);
33 I(!used);
34 initialized = true;
35 value = val;
36 }
37 T const & get()
38 {
39 I(initialized);
40 used = true;
41 return value;
42 }
43 T const & get_but_unused()
44 {
45 I(initialized);
46 return value;
47 }
48 void may_not_initialize()
49 {
50 I(!initialized);
51 very_uninitialized = true;
52 }
53 // for unit tests
54 void unset()
55 {
56 used = initialized = very_uninitialized = false;
57 }
58 T value;
59 bool initialized, used, very_uninitialized;
60 access_tracker() : initialized(false), used(false), very_uninitialized(false) {};
61};
62
63// paths to use in interpreting paths from various sources,
64// conceptually:
65// working_root / initial_rel_path == initial_abs_path
66
67// initial_abs_path is for interpreting relative system_path's
68static access_tracker<system_path> initial_abs_path;
69// initial_rel_path is for interpreting external file_path's
70// for now we just make it an fs::path for convenience; we used to make it a
71// file_path, but then you can't run monotone from inside the MT/ dir (even
72// when referring to files outside the MT/ dir).
73static access_tracker<fs::path> initial_rel_path;
74// working_root is for converting file_path's and bookkeeping_path's to
75// system_path's.
76static access_tracker<system_path> working_root;
77
78bookkeeping_path const bookkeeping_root("MT");
79
80void
81save_initial_path()
82{
83 // FIXME: BUG: this only works if the current working dir is in utf8
84 initial_abs_path.set(system_path(get_current_working_dir()), false);
85 // We still use boost::fs, so let's continue to initialize it properly.
86 fs::initial_path();
87 fs::path::default_name_check(fs::native);
88 L(F("initial abs path is: %s") % initial_abs_path.get_but_unused());
89}
90
91///////////////////////////////////////////////////////////////////////////
92// verifying that internal paths are indeed normalized.
93// this code must be superfast
94///////////////////////////////////////////////////////////////////////////
95
96// normalized means:
97// -- / as path separator
98// -- not an absolute path (on either posix or win32)
99// operationally, this means: first character != '/', first character != '\',
100// second character != ':'
101// -- no illegal characters
102// -- 0x00 -- 0x1f, 0x7f, \ are the illegal characters. \ is illegal
103// unconditionally to prevent people checking in files on posix that
104// have a different interpretation on win32
105// -- (may want to allow 0x0a and 0x0d (LF and CR) in the future, but this
106// is blocked on manifest format changing)
107// (also requires changes to 'automate inventory', possibly others, to
108// handle quoting)
109// -- no doubled /'s
110// -- no trailing /
111// -- no "." or ".." path components
112static inline bool
113bad_component(std::string const & component)
114{
115 if (component == "")
116 return true;
117 if (component == ".")
118 return true;
119 if (component == "..")
120 return true;
121 return false;
122}
123
124static inline bool
125fully_normalized_path(std::string const & path)
126{
127 // FIXME: probably should make this a 256-byte static lookup table
128 const static std::string bad_chars = std::string("\\") + constants::illegal_path_bytes + std::string(1, '\0');
129
130 // empty path is fine
131 if (path.empty())
132 return true;
133 // could use is_absolute_somewhere, but this is the only part of it that
134 // wouldn't be redundant
135 if (path.size() > 1 && path[1] == ':')
136 return false;
137 // first scan for completely illegal bytes
138 if (path.find_first_of(bad_chars) != std::string::npos)
139 return false;
140 // now check each component
141 std::string::size_type start, stop;
142 start = 0;
143 while (1)
144 {
145 stop = path.find('/', start);
146 if (stop == std::string::npos)
147 {
148 if (bad_component(path.substr(start)))
149 return false;
150 break;
151 }
152 if (bad_component(path.substr(start, stop - start)))
153 return false;
154 start = stop + 1;
155 }
156 return true;
157}
158
159static inline bool
160in_bookkeeping_dir(std::string const & path)
161{
162 return path == "MT" || (path.size() >= 3 && (path.substr(0, 3) == "MT/"));
163}
164
165static inline bool
166is_valid_internal(std::string const & path)
167{
168 return (fully_normalized_path(path)
169 && !in_bookkeeping_dir(path));
170}
171
172file_path::file_path(file_path::source_type type, std::string const & path)
173{
174 switch (type)
175 {
176 case internal:
177 data = path;
178 break;
179 case external:
180 if (!initial_rel_path.initialized)
181 {
182 // we are not in a working directory; treat this as an internal
183 // path, and set the access_tracker() into a very uninitialised
184 // state so that we will hit an exception if we do eventually
185 // enter a working directory
186 initial_rel_path.may_not_initialize();
187 data = path;
188 N(is_valid_internal(path) && !in_bookkeeping_dir(path),
189 F("path '%s' is invalid") % path);
190 break;
191 }
192 N(!path.empty(), F("empty path '%s' is invalid") % path);
193 fs::path out, base, relative;
194 try
195 {
196 base = initial_rel_path.get();
197 // the fs::native is needed to get it to accept paths like ".foo".
198 relative = fs::path(path, fs::native);
199 out = (base / relative).normalize();
200 }
201 catch (std::exception & e)
202 {
203 N(false, F("path '%s' is invalid") % path);
204 }
205 data = utf8(out.string());
206 if (data() == ".")
207 data = std::string("");
208 N(!relative.has_root_path(),
209 F("absolute path '%s' is invalid") % relative.string());
210 N(fully_normalized_path(data()), F("path '%s' is invalid") % data);
211 N(!in_bookkeeping_dir(data()), F("path '%s' is in bookkeeping dir") % data);
212 break;
213 }
214 MM(data);
215 I(is_valid_internal(data()));
216}
217
218bookkeeping_path::bookkeeping_path(std::string const & path)
219{
220 I(fully_normalized_path(path));
221 I(in_bookkeeping_dir(path));
222 data = path;
223}
224
225bool
226bookkeeping_path::is_bookkeeping_path(std::string const & path)
227{
228 return in_bookkeeping_dir(path);
229}
230
231///////////////////////////////////////////////////////////////////////////
232// splitting/joining
233// this code must be superfast
234// it depends very much on knowing that it can only be applied to fully
235// normalized, relative, paths.
236///////////////////////////////////////////////////////////////////////////
237
238static interner<path_component> pc_interner("", the_null_component);
239
240// This function takes a vector of path components and joins them into a
241// single file_path. Valid input may be a single-element vector whose sole
242// element is the empty path component (""); this represents the null path,
243// which we use to represent non-existent files. Alternatively, input may be
244// a multi-element vector, in which case all elements of the vector are
245// required to be non-null. The following are valid inputs (with strings
246// replaced by their interned version, of course):
247// - [""]
248// - ["foo"]
249// - ["foo", "bar"]
250// The following are not:
251// - []
252// - ["foo", ""]
253// - ["", "bar"]
254file_path::file_path(split_path const & sp)
255{
256 split_path::const_iterator i = sp.begin();
257 I(i != sp.end());
258 if (sp.size() > 1)
259 I(!null_name(*i));
260 std::string tmp = pc_interner.lookup(*i);
261 I(tmp != bookkeeping_root.as_internal());
262 for (++i; i != sp.end(); ++i)
263 {
264 I(!null_name(*i));
265 tmp += "/";
266 tmp += pc_interner.lookup(*i);
267 }
268 data = tmp;
269}
270
271//
272// this takes a path of the form
273//
274// "p[0]/p[1]/.../p[n-1]/p[n]"
275//
276// and fills in a vector of paths corresponding to p[0] ... p[n-1]
277//
278// FIXME: this code carefully duplicates the behavior of the old path
279// splitting functions, in that it is _not_ the inverse to the above joining
280// function. The difference is that if you pass a null path (the one
281// represented by the empty string, or 'file_path()'), then it will return an
282// _empty_ vector. This vector will not be suitable to pass to the above path
283// joiner; to get the null path back again, you have to pass the above
284// function a single-element vector containing a the null component.
285//
286// Why does it work this way? Because that's what the old code did, and
287// that's what change_set.cc was written around; and it's much much easier to
288// make this code do something weird than to try and fix change_set.cc. When
289// change_set.cc is rewritten, however, you should revisit the semantics of
290// this function.
291void
292file_path::split(split_path & sp) const
293{
294 sp.clear();
295 if (empty())
296 return;
297 std::string::size_type start, stop;
298 start = 0;
299 std::string const & s = data();
300 while (1)
301 {
302 stop = s.find('/', start);
303 if (stop < 0 || stop > s.length())
304 {
305 sp.push_back(pc_interner.intern(s.substr(start)));
306 break;
307 }
308 sp.push_back(pc_interner.intern(s.substr(start, stop - start)));
309 start = stop + 1;
310 }
311}
312
313///////////////////////////////////////////////////////////////////////////
314// localizing file names (externalizing them)
315// this code must be superfast when there is no conversion needed
316///////////////////////////////////////////////////////////////////////////
317
318std::string
319any_path::as_external() const
320{
321#ifdef __APPLE__
322 // on OS X paths for the filesystem/kernel are UTF-8 encoded, regardless of
323 // locale.
324 return data();
325#else
326 // on normal systems we actually have some work to do, alas.
327 // not much, though, because utf8_to_system does all the hard work. it is
328 // carefully optimized. do not screw it up.
329 external out;
330 utf8_to_system(data, out);
331 return out();
332#endif
333}
334
335///////////////////////////////////////////////////////////////////////////
336// writing out paths
337///////////////////////////////////////////////////////////////////////////
338
339std::ostream &
340operator <<(std::ostream & o, any_path const & a)
341{
342 o << a.as_internal();
343 return o;
344}
345
346///////////////////////////////////////////////////////////////////////////
347// path manipulation
348// this code's speed does not matter much
349///////////////////////////////////////////////////////////////////////////
350
351static bool
352is_absolute_here(std::string const & path)
353{
354 if (path.empty())
355 return false;
356 if (path[0] == '/')
357 return true;
358#ifdef WIN32
359 if (path[0] == '\\')
360 return true;
361 if (path.size() > 1 && path[1] == ':')
362 return true;
363#endif
364 return false;
365}
366
367static inline bool
368is_absolute_somewhere(std::string const & path)
369{
370 if (path.empty())
371 return false;
372 if (path[0] == '/')
373 return true;
374 if (path[0] == '\\')
375 return true;
376 if (path.size() > 1 && path[1] == ':')
377 return true;
378 return false;
379}
380
381file_path
382file_path::operator /(std::string const & to_append) const
383{
384 I(!is_absolute_somewhere(to_append));
385 if (empty())
386 return file_path_internal(to_append);
387 else
388 return file_path_internal(data() + "/" + to_append);
389}
390
391bookkeeping_path
392bookkeeping_path::operator /(std::string const & to_append) const
393{
394 I(!is_absolute_somewhere(to_append));
395 I(!empty());
396 return bookkeeping_path(data() + "/" + to_append);
397}
398
399system_path
400system_path::operator /(std::string const & to_append) const
401{
402 I(!empty());
403 I(!is_absolute_here(to_append));
404 return system_path(data() + "/" + to_append);
405}
406
407///////////////////////////////////////////////////////////////////////////
408// system_path
409///////////////////////////////////////////////////////////////////////////
410
411static std::string
412normalize_out_dots(std::string const & path)
413{
414#ifdef WIN32
415 return fs::path(path, fs::native).normalize().string();
416#else
417 return fs::path(path, fs::native).normalize().native_file_string();
418#endif
419}
420
421system_path::system_path(any_path const & other, bool in_true_working_copy)
422{
423 I(!is_absolute_here(other.as_internal()));
424 system_path wr;
425 if (in_true_working_copy)
426 wr = working_root.get();
427 else
428 wr = working_root.get_but_unused();
429 data = normalize_out_dots((wr / other.as_internal()).as_internal());
430}
431
432static inline std::string const_system_path(utf8 const & path)
433{
434 N(!path().empty(), F("invalid path ''"));
435 std::string expanded = tilde_expand(path)();
436 if (is_absolute_here(expanded))
437 return normalize_out_dots(expanded);
438 else
439 return normalize_out_dots((initial_abs_path.get() / expanded).as_internal());
440}
441
442system_path::system_path(std::string const & path)
443{
444 data = const_system_path(path);
445}
446
447system_path::system_path(utf8 const & path)
448{
449 data = const_system_path(path);
450}
451
452///////////////////////////////////////////////////////////////////////////
453// working copy (and path roots) handling
454///////////////////////////////////////////////////////////////////////////
455
456bool
457find_and_go_to_working_copy(system_path const & search_root)
458{
459 // unimplemented
460 fs::path root(search_root.as_external(), fs::native);
461 fs::path bookdir(bookkeeping_root.as_external(), fs::native);
462 fs::path current(fs::initial_path());
463 fs::path removed;
464 fs::path check = current / bookdir;
465
466 L(F("searching for '%s' directory with root '%s'\n")
467 % bookdir.string()
468 % root.string());
469
470 while (current != root
471 && current.has_branch_path()
472 && current.has_leaf()
473 && !fs::exists(check))
474 {
475 L(F("'%s' not found in '%s' with '%s' removed\n")
476 % bookdir.string() % current.string() % removed.string());
477 removed = fs::path(current.leaf(), fs::native) / removed;
478 current = current.branch_path();
479 check = current / bookdir;
480 }
481
482 L(F("search for '%s' ended at '%s' with '%s' removed\n")
483 % bookdir.string() % current.string() % removed.string());
484
485 if (!fs::exists(check))
486 {
487 L(F("'%s' does not exist\n") % check.string());
488 return false;
489 }
490
491 if (!fs::is_directory(check))
492 {
493 L(F("'%s' is not a directory\n") % check.string());
494 return false;
495 }
496
497 // check for MT/. and MT/.. to see if mt dir is readable
498 if (!fs::exists(check / ".") || !fs::exists(check / ".."))
499 {
500 L(F("problems with '%s' (missing '.' or '..')\n") % check.string());
501 return false;
502 }
503
504 working_root.set(current.native_file_string(), true);
505 initial_rel_path.set(removed, true);
506
507 L(F("working root is '%s'") % working_root.get_but_unused());
508 L(F("initial relative path is '%s'") % initial_rel_path.get_but_unused().string());
509
510 change_current_working_dir(working_root.get_but_unused());
511
512 return true;
513}
514
515void
516go_to_working_copy(system_path const & new_working_copy)
517{
518 working_root.set(new_working_copy, true);
519 initial_rel_path.set(fs::path(), true);
520 change_current_working_dir(new_working_copy);
521}
522
523///////////////////////////////////////////////////////////////////////////
524// tests
525///////////////////////////////////////////////////////////////////////////
526
527#ifdef BUILD_UNIT_TESTS
528#include "unit_tests.hh"
529
530static void test_null_name()
531{
532 BOOST_CHECK(null_name(the_null_component));
533}
534
535static void test_file_path_internal()
536{
537 char const * baddies[] = {"/foo",
538 "foo//bar",
539 "foo/../bar",
540 "../bar",
541 "MT/blah",
542 "foo/bar/",
543 "foo/./bar",
544 "./foo",
545 ".",
546 "..",
547 "c:\\foo",
548 "c:foo",
549 "c:/foo",
550 0 };
551 initial_rel_path.unset();
552 initial_rel_path.set(fs::path(), true);
553 for (char const ** c = baddies; *c; ++c)
554 {
555 BOOST_CHECK_THROW(file_path_internal(*c), std::logic_error);
556 }
557 initial_rel_path.unset();
558 initial_rel_path.set(fs::path("blah/blah/blah", fs::native), true);
559 for (char const ** c = baddies; *c; ++c)
560 {
561 BOOST_CHECK_THROW(file_path_internal(*c), std::logic_error);
562 }
563
564 BOOST_CHECK(file_path().empty());
565 BOOST_CHECK(file_path_internal("").empty());
566
567 char const * goodies[] = {"",
568 "a",
569 "foo",
570 "foo/bar/baz",
571 "foo/bar.baz",
572 "foo/with-hyphen/bar",
573 "foo/with_underscore/bar",
574 "foo/with,other+@weird*%#$=stuff/bar",
575 ".foo/bar",
576 "..foo/bar",
577 "MTfoo/bar",
578 "foo:bar",
579 0 };
580
581 for (int i = 0; i < 2; ++i)
582 {
583 initial_rel_path.unset();
584 initial_rel_path.set(i ? fs::path()
585 : fs::path("blah/blah/blah", fs::native),
586 true);
587 for (char const ** c = goodies; *c; ++c)
588 {
589 file_path fp = file_path_internal(*c);
590 BOOST_CHECK(fp.as_internal() == *c);
591 BOOST_CHECK(file_path_internal(fp.as_internal()) == fp);
592 std::vector<path_component> split_test;
593 fp.split(split_test);
594 if (fp.empty())
595 BOOST_CHECK(split_test.empty());
596 else
597 {
598 BOOST_CHECK(!split_test.empty());
599 file_path fp2(split_test);
600 BOOST_CHECK(fp == fp2);
601 }
602 for (std::vector<path_component>::const_iterator i = split_test.begin();
603 i != split_test.end(); ++i)
604 BOOST_CHECK(!null_name(*i));
605 }
606 }
607
608 initial_rel_path.unset();
609}
610
611static void check_fp_normalizes_to(char * before, char * after)
612{
613 L(F("check_fp_normalizes_to: '%s' -> '%s'") % before % after);
614 file_path fp = file_path_external(std::string(before));
615 L(F(" (got: %s)") % fp);
616 BOOST_CHECK(fp.as_internal() == after);
617 BOOST_CHECK(file_path_internal(fp.as_internal()) == fp);
618 // we compare after to the external form too, since as far as we know
619 // relative normalized posix paths are always good win32 paths too
620 BOOST_CHECK(fp.as_external() == after);
621 std::vector<path_component> split_test;
622 fp.split(split_test);
623 if (fp.empty())
624 BOOST_CHECK(split_test.empty());
625 else
626 {
627 BOOST_CHECK(!split_test.empty());
628 file_path fp2(split_test);
629 BOOST_CHECK(fp == fp2);
630 }
631 for (std::vector<path_component>::const_iterator i = split_test.begin();
632 i != split_test.end(); ++i)
633 BOOST_CHECK(!null_name(*i));
634}
635
636static void test_file_path_external_null_prefix()
637{
638 initial_rel_path.unset();
639 initial_rel_path.set(fs::path(), true);
640
641 char const * baddies[] = {"/foo",
642 "../bar",
643 "MT/blah",
644 "MT",
645 "//blah",
646 "\\foo",
647 "..",
648 "c:\\foo",
649 "c:foo",
650 "c:/foo",
651 "",
652 0 };
653 for (char const ** c = baddies; *c; ++c)
654 {
655 L(F("test_file_path_external_null_prefix: trying baddie: %s") % *c);
656 BOOST_CHECK_THROW(file_path_external(utf8(*c)), informative_failure);
657 }
658
659 check_fp_normalizes_to("a", "a");
660 check_fp_normalizes_to("foo", "foo");
661 check_fp_normalizes_to("foo/bar", "foo/bar");
662 check_fp_normalizes_to("foo/bar/baz", "foo/bar/baz");
663 check_fp_normalizes_to("foo/bar.baz", "foo/bar.baz");
664 check_fp_normalizes_to("foo/with-hyphen/bar", "foo/with-hyphen/bar");
665 check_fp_normalizes_to("foo/with_underscore/bar", "foo/with_underscore/bar");
666 check_fp_normalizes_to(".foo/bar", ".foo/bar");
667 check_fp_normalizes_to("..foo/bar", "..foo/bar");
668 check_fp_normalizes_to(".", "");
669#ifndef WIN32
670 check_fp_normalizes_to("foo:bar", "foo:bar");
671#endif
672 check_fp_normalizes_to("foo/with,other+@weird*%#$=stuff/bar",
673 "foo/with,other+@weird*%#$=stuff/bar");
674
675 // Why are these tests with // in them commented out? because boost::fs
676 // sucks and can't normalize them. FIXME.
677 //check_fp_normalizes_to("foo//bar", "foo/bar");
678 check_fp_normalizes_to("foo/../bar", "bar");
679 check_fp_normalizes_to("foo/bar/", "foo/bar");
680 check_fp_normalizes_to("foo/./bar/", "foo/bar");
681 check_fp_normalizes_to("./foo", "foo");
682 //check_fp_normalizes_to("foo///.//", "foo");
683
684 initial_rel_path.unset();
685}
686
687static void test_file_path_external_prefix_MT()
688{
689 initial_rel_path.unset();
690 initial_rel_path.set(fs::path("MT"), true);
691
692 BOOST_CHECK_THROW(file_path_external(utf8("foo")), informative_failure);
693 BOOST_CHECK_THROW(file_path_external(utf8(".")), informative_failure);
694 BOOST_CHECK_THROW(file_path_external(utf8("./blah")), informative_failure);
695 check_fp_normalizes_to("..", "");
696 check_fp_normalizes_to("../foo", "foo");
697}
698
699static void test_file_path_external_prefix_a_b()
700{
701 initial_rel_path.unset();
702 initial_rel_path.set(fs::path("a/b"), true);
703
704 char const * baddies[] = {"/foo",
705 "../../../bar",
706 "../../..",
707 "../../MT",
708 "../../MT/foo",
709 "//blah",
710 "\\foo",
711 "c:\\foo",
712#ifdef WIN32
713 "c:foo",
714 "c:/foo",
715#endif
716 "",
717 0 };
718 for (char const ** c = baddies; *c; ++c)
719 {
720 L(F("test_file_path_external_prefix_a_b: trying baddie: %s") % *c);
721 BOOST_CHECK_THROW(file_path_external(utf8(*c)), informative_failure);
722 }
723
724 check_fp_normalizes_to("foo", "a/b/foo");
725 check_fp_normalizes_to("a", "a/b/a");
726 check_fp_normalizes_to("foo/bar", "a/b/foo/bar");
727 check_fp_normalizes_to("foo/bar/baz", "a/b/foo/bar/baz");
728 check_fp_normalizes_to("foo/bar.baz", "a/b/foo/bar.baz");
729 check_fp_normalizes_to("foo/with-hyphen/bar", "a/b/foo/with-hyphen/bar");
730 check_fp_normalizes_to("foo/with_underscore/bar", "a/b/foo/with_underscore/bar");
731 check_fp_normalizes_to(".foo/bar", "a/b/.foo/bar");
732 check_fp_normalizes_to("..foo/bar", "a/b/..foo/bar");
733 check_fp_normalizes_to(".", "a/b");
734#ifndef WIN32
735 check_fp_normalizes_to("foo:bar", "a/b/foo:bar");
736#endif
737 check_fp_normalizes_to("foo/with,other+@weird*%#$=stuff/bar",
738 "a/b/foo/with,other+@weird*%#$=stuff/bar");
739 // why are the tests with // in them commented out? because boost::fs sucks
740 // and can't normalize them. FIXME.
741 //check_fp_normalizes_to("foo//bar", "a/b/foo/bar");
742 check_fp_normalizes_to("foo/../bar", "a/b/bar");
743 check_fp_normalizes_to("foo/bar/", "a/b/foo/bar");
744 check_fp_normalizes_to("foo/./bar/", "a/b/foo/bar");
745 check_fp_normalizes_to("./foo", "a/b/foo");
746 //check_fp_normalizes_to("foo///.//", "a/b/foo");
747 // things that would have been bad without the initial_rel_path:
748 check_fp_normalizes_to("../foo", "a/foo");
749 check_fp_normalizes_to("..", "a");
750 check_fp_normalizes_to("../..", "");
751 check_fp_normalizes_to("MT/foo", "a/b/MT/foo");
752 check_fp_normalizes_to("MT", "a/b/MT");
753#ifndef WIN32
754 check_fp_normalizes_to("c:foo", "a/b/c:foo");
755 check_fp_normalizes_to("c:/foo", "a/b/c:/foo");
756#endif
757
758 initial_rel_path.unset();
759}
760
761static void test_split_join()
762{
763 file_path fp1 = file_path_internal("foo/bar/baz");
764 file_path fp2 = file_path_internal("bar/baz/foo");
765 typedef std::vector<path_component> pcv;
766 pcv split1, split2;
767 fp1.split(split1);
768 fp2.split(split2);
769 BOOST_CHECK(fp1 == file_path(split1));
770 BOOST_CHECK(fp2 == file_path(split2));
771 BOOST_CHECK(!(fp1 == file_path(split2)));
772 BOOST_CHECK(!(fp2 == file_path(split1)));
773 BOOST_CHECK(split1.size() == 3);
774 BOOST_CHECK(split2.size() == 3);
775 BOOST_CHECK(split1[0] != split1[1]);
776 BOOST_CHECK(split1[0] != split1[2]);
777 BOOST_CHECK(split1[1] != split1[2]);
778 BOOST_CHECK(!null_name(split1[0])
779 && !null_name(split1[1])
780 && !null_name(split1[2]));
781 BOOST_CHECK(split1[0] == split2[2]);
782 BOOST_CHECK(split1[1] == split2[0]);
783 BOOST_CHECK(split1[2] == split2[1]);
784
785 file_path fp3 = file_path_internal("");
786 pcv split3;
787 fp3.split(split3);
788 BOOST_CHECK(split3.empty());
789
790 pcv split4;
791 // this comparison tricks the compiler into not completely eliminating this
792 // code as dead...
793 BOOST_CHECK_THROW(file_path(split4) == file_path(), std::logic_error);
794 split4.push_back(the_null_component);
795 BOOST_CHECK(file_path(split4) == file_path());
796 split4.clear();
797 split4.push_back(split1[0]);
798 split4.push_back(the_null_component);
799 split4.push_back(split1[0]);
800 // this comparison tricks the compiler into not completely eliminating this
801 // code as dead...
802 BOOST_CHECK_THROW(file_path(split4) == file_path(), std::logic_error);
803
804 // Make sure that we can't use joining to create a path into the bookkeeping
805 // dir
806 pcv split_mt1, split_mt2;
807 file_path_internal("foo/MT").split(split_mt1);
808 BOOST_CHECK(split_mt1.size() == 2);
809 split_mt2.push_back(split_mt1[1]);
810 // split_mt2 now contains the component "MT"
811 BOOST_CHECK_THROW(file_path(split_mt2) == file_path(), std::logic_error);
812 split_mt2.push_back(split_mt1[0]);
813 // split_mt2 now contains the components "MT", "foo" in that order
814 // this comparison tricks the compiler into not completely eliminating this
815 // code as dead...
816 BOOST_CHECK_THROW(file_path(split_mt2) == file_path(), std::logic_error);
817}
818
819static void check_bk_normalizes_to(char * before, char * after)
820{
821 bookkeeping_path bp(bookkeeping_root / before);
822 L(F("normalizing %s to %s (got %s)") % before % after % bp);
823 BOOST_CHECK(bp.as_external() == after);
824 BOOST_CHECK(bookkeeping_path(bp.as_internal()).as_internal() == bp.as_internal());
825}
826
827static void test_bookkeeping_path()
828{
829 char const * baddies[] = {"/foo",
830 "foo//bar",
831 "foo/../bar",
832 "../bar",
833 "foo/bar/",
834 "foo/./bar",
835 "./foo",
836 ".",
837 "..",
838 "c:\\foo",
839 "c:foo",
840 "c:/foo",
841 "",
842 "a:b",
843 0 };
844 std::string tmp_path_string;
845
846 for (char const ** c = baddies; *c; ++c)
847 {
848 L(F("test_bookkeeping_path baddie: trying '%s'") % *c);
849 BOOST_CHECK_THROW(bookkeeping_path(tmp_path_string.assign(*c)), std::logic_error);
850 BOOST_CHECK_THROW(bookkeeping_root / tmp_path_string.assign(*c), std::logic_error);
851 }
852 BOOST_CHECK_THROW(bookkeeping_path(tmp_path_string.assign("foo/bar")), std::logic_error);
853 BOOST_CHECK_THROW(bookkeeping_path(tmp_path_string.assign("a")), std::logic_error);
854
855 check_bk_normalizes_to("a", "MT/a");
856 check_bk_normalizes_to("foo", "MT/foo");
857 check_bk_normalizes_to("foo/bar", "MT/foo/bar");
858 check_bk_normalizes_to("foo/bar/baz", "MT/foo/bar/baz");
859}
860
861static void check_system_normalizes_to(char * before, char * after)
862{
863 system_path sp(before);
864 L(F("normalizing '%s' to '%s' (got '%s')") % before % after % sp);
865 BOOST_CHECK(sp.as_external() == after);
866 BOOST_CHECK(system_path(sp.as_internal()).as_internal() == sp.as_internal());
867}
868
869static void test_system_path()
870{
871 initial_abs_path.unset();
872 initial_abs_path.set(system_path("/a/b"), true);
873
874 BOOST_CHECK_THROW(system_path(""), informative_failure);
875
876 check_system_normalizes_to("foo", "/a/b/foo");
877 check_system_normalizes_to("foo/bar", "/a/b/foo/bar");
878 check_system_normalizes_to("/foo/bar", "/foo/bar");
879 check_system_normalizes_to("//foo/bar", "//foo/bar");
880#ifdef WIN32
881 check_system_normalizes_to("c:foo", "c:foo");
882 check_system_normalizes_to("c:/foo", "c:/foo");
883 check_system_normalizes_to("c:\\foo", "c:/foo");
884#else
885 check_system_normalizes_to("c:foo", "/a/b/c:foo");
886 check_system_normalizes_to("c:/foo", "/a/b/c:/foo");
887 check_system_normalizes_to("c:\\foo", "/a/b/c:\\foo");
888 check_system_normalizes_to("foo:bar", "/a/b/foo:bar");
889#endif
890 // we require that system_path normalize out ..'s, because of the following
891 // case:
892 // /work mkdir newdir
893 // /work$ cd newdir
894 // /work/newdir$ monotone setup --db=../foo.db
895 // Now they have either "/work/foo.db" or "/work/newdir/../foo.db" in
896 // MT/options
897 // /work/newdir$ cd ..
898 // /work$ mv newdir newerdir # better name
899 // Oops, now, if we stored the version with ..'s in, this working directory
900 // is broken.
901 check_system_normalizes_to("../foo", "/a/foo");
902 check_system_normalizes_to("foo/..", "/a/b");
903 check_system_normalizes_to("/foo/bar/..", "/foo");
904 check_system_normalizes_to("/foo/..", "/");
905 // can't do particularly interesting checking of tilde expansion, but at
906 // least we can check that it's doing _something_...
907 std::string tilde_expanded = system_path("~/foo").as_external();
908#ifdef WIN32
909 BOOST_CHECK(tilde_expanded[1] == ':');
910#else
911 BOOST_CHECK(tilde_expanded[0] == '/');
912#endif
913 BOOST_CHECK(tilde_expanded.find('~') == std::string::npos);
914 // and check for the weird WIN32 version
915#ifdef WIN32
916 std::string tilde_expanded2 = system_path("~this_user_does_not_exist_anywhere").as_external();
917 BOOST_CHECK(tilde_expanded2[0] = '/');
918 BOOST_CHECK(tilde_expanded2.find('~') == std::string::npos);
919#else
920 BOOST_CHECK_THROW(system_path("~this_user_does_not_exist_anywhere"), informative_failure);
921#endif
922
923 // finally, make sure that the copy-from-any_path constructor works right
924 // in particular, it should interpret the paths it gets as being relative to
925 // the project root, not the initial path
926 working_root.unset();
927 working_root.set(system_path("/working/root"), true);
928 initial_rel_path.unset();
929 initial_rel_path.set(fs::path("rel/initial"), true);
930
931 BOOST_CHECK(system_path(system_path("foo/bar")).as_internal() == "/a/b/foo/bar");
932 BOOST_CHECK(!working_root.used);
933 BOOST_CHECK(system_path(system_path("/foo/bar")).as_internal() == "/foo/bar");
934 BOOST_CHECK(!working_root.used);
935 BOOST_CHECK(system_path(file_path_internal("foo/bar"), false).as_internal()
936 == "/working/root/foo/bar");
937 BOOST_CHECK(!working_root.used);
938 BOOST_CHECK(system_path(file_path_internal("foo/bar")).as_internal()
939 == "/working/root/foo/bar");
940 BOOST_CHECK(working_root.used);
941 BOOST_CHECK(system_path(file_path_external(std::string("foo/bar"))).as_external()
942 == "/working/root/rel/initial/foo/bar");
943 file_path a_file_path;
944 BOOST_CHECK(system_path(a_file_path).as_external()
945 == "/working/root");
946 BOOST_CHECK(system_path(bookkeeping_path("MT/foo/bar")).as_internal()
947 == "/working/root/MT/foo/bar");
948 BOOST_CHECK(system_path(bookkeeping_root).as_internal()
949 == "/working/root/MT");
950 initial_abs_path.unset();
951 working_root.unset();
952 initial_rel_path.unset();
953}
954
955static void test_access_tracker()
956{
957 access_tracker<int> a;
958 BOOST_CHECK_THROW(a.get(), std::logic_error);
959 a.set(1, false);
960 BOOST_CHECK_THROW(a.set(2, false), std::logic_error);
961 a.set(2, true);
962 BOOST_CHECK_THROW(a.set(3, false), std::logic_error);
963 BOOST_CHECK(a.get() == 2);
964 BOOST_CHECK_THROW(a.set(3, true), std::logic_error);
965 a.unset();
966 a.may_not_initialize();
967 BOOST_CHECK_THROW(a.set(1, false), std::logic_error);
968 BOOST_CHECK_THROW(a.set(2, true), std::logic_error);
969 a.unset();
970 a.set(1, false);
971 BOOST_CHECK_THROW(a.may_not_initialize(), std::logic_error);
972}
973
974void add_paths_tests(test_suite * suite)
975{
976 I(suite);
977 suite->add(BOOST_TEST_CASE(&test_null_name));
978 suite->add(BOOST_TEST_CASE(&test_file_path_internal));
979 suite->add(BOOST_TEST_CASE(&test_file_path_external_null_prefix));
980 suite->add(BOOST_TEST_CASE(&test_file_path_external_prefix_MT));
981 suite->add(BOOST_TEST_CASE(&test_file_path_external_prefix_a_b));
982 suite->add(BOOST_TEST_CASE(&test_split_join));
983 suite->add(BOOST_TEST_CASE(&test_bookkeeping_path));
984 suite->add(BOOST_TEST_CASE(&test_system_path));
985 suite->add(BOOST_TEST_CASE(&test_access_tracker));
986}
987
988#endif // BUILD_UNIT_TESTS

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status