monotone

monotone Mtn Source Tree

Root/work.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include "base.hh"
11#include "work.hh"
12
13#include <ostream>
14#include <sstream>
15#include <cstring>
16#include <cerrno>
17#include <queue>
18
19#include <boost/shared_ptr.hpp>
20
21#include "lexical_cast.hh"
22#include "basic_io.hh"
23#include "cset.hh"
24#include "file_io.hh"
25#include "platform-wrapped.hh"
26#include "restrictions.hh"
27#include "sanity.hh"
28#include "safe_map.hh"
29#include "simplestring_xform.hh"
30#include "revision.hh"
31#include "inodeprint.hh"
32#include "diff_patch.hh"
33#include "ui.hh"
34#include "charset.hh"
35#include "app_state.hh"
36#include "database.hh"
37#include "roster.hh"
38#include "transforms.hh"
39
40using std::deque;
41using std::exception;
42using std::make_pair;
43using std::map;
44using std::pair;
45using std::set;
46using std::string;
47using std::vector;
48
49using boost::lexical_cast;
50using boost::shared_ptr;
51
52// workspace / book-keeping file code
53
54static char const inodeprints_file_name[] = "inodeprints";
55static char const local_dump_file_name[] = "debug";
56static char const options_file_name[] = "options";
57static char const user_log_file_name[] = "log";
58static char const revision_file_name[] = "revision";
59
60static void
61get_revision_path(bookkeeping_path & m_path)
62{
63 m_path = bookkeeping_root / revision_file_name;
64 L(FL("revision path is %s") % m_path);
65}
66
67static void
68get_options_path(bookkeeping_path & o_path)
69{
70 o_path = bookkeeping_root / options_file_name;
71 L(FL("options path is %s") % o_path);
72}
73
74static void
75get_options_path(system_path const & workspace, system_path & o_path)
76{
77 o_path = workspace / bookkeeping_root_component / options_file_name;
78 L(FL("options path is %s") % o_path);
79}
80
81static void
82get_inodeprints_path(bookkeeping_path & ip_path)
83{
84 ip_path = bookkeeping_root / inodeprints_file_name;
85 L(FL("inodeprints path is %s") % ip_path);
86}
87
88static void
89get_user_log_path(bookkeeping_path & ul_path)
90{
91 ul_path = bookkeeping_root / user_log_file_name;
92 L(FL("user log path is %s") % ul_path);
93}
94
95//
96
97bool
98directory_is_workspace(system_path const & dir)
99{
100 // as far as the users of this function are concerned, a version 0
101 // workspace (MT directory instead of _MTN) does not count.
102 return directory_exists(dir / bookkeeping_root_component);
103}
104
105bool workspace::found;
106bool workspace::branch_is_sticky;
107
108void
109workspace::require_workspace()
110{
111 N(workspace::found,
112 F("workspace required but not found"));
113}
114
115void
116workspace::require_workspace(i18n_format const & explanation)
117{
118 N(workspace::found,
119 F("workspace required but not found\n%s") % explanation.str());
120}
121
122void
123workspace::create_workspace(options const & opts,
124 lua_hooks & lua,
125 system_path const & new_dir)
126{
127 N(!new_dir.empty(), F("invalid directory ''"));
128
129 L(FL("creating workspace in %s") % new_dir);
130
131 mkdir_p(new_dir);
132 go_to_workspace(new_dir);
133 mark_std_paths_used();
134
135 N(!directory_exists(bookkeeping_root),
136 F("monotone bookkeeping directory '%s' already exists in '%s'")
137 % bookkeeping_root % new_dir);
138
139 L(FL("creating bookkeeping directory '%s' for workspace in '%s'")
140 % bookkeeping_root % new_dir);
141
142 mkdir_p(bookkeeping_root);
143
144 workspace::found = true;
145 workspace::set_ws_options(opts, true);
146 workspace::write_ws_format();
147
148 data empty;
149 bookkeeping_path ul_path;
150 get_user_log_path(ul_path);
151 write_data(ul_path, empty);
152
153 if (lua.hook_use_inodeprints())
154 {
155 bookkeeping_path ip_path;
156 get_inodeprints_path(ip_path);
157 write_data(ip_path, empty);
158 }
159
160 bookkeeping_path dump_path;
161 workspace::get_local_dump_path(dump_path);
162 // The 'false' means that, e.g., if we're running checkout,
163 // then it's okay for dumps to go into our starting working
164 // dir's _MTN rather than the new workspace dir's _MTN.
165 global_sanity.set_dump_path(system_path(dump_path, false).as_external());
166}
167
168// Normal-use constructor.
169workspace::workspace(app_state & app, bool writeback_options)
170 : lua(app.lua)
171{
172 require_workspace();
173 if (writeback_options)
174 set_ws_options(app.opts, false);
175}
176
177workspace::workspace(app_state & app, i18n_format const & explanation,
178 bool writeback_options)
179 : lua(app.lua)
180{
181 require_workspace(explanation);
182 if (writeback_options)
183 set_ws_options(app.opts, false);
184}
185
186workspace::workspace(options const & opts, lua_hooks & lua,
187 i18n_format const & explanation, bool writeback_options)
188 : lua(lua)
189{
190 require_workspace(explanation);
191 if (writeback_options)
192 set_ws_options(opts, false);
193}
194
195// routines for manipulating the bookkeeping directory
196
197// revision file contains a partial revision describing the workspace
198void
199workspace::get_work_rev(revision_t & rev)
200{
201 bookkeeping_path rev_path;
202 get_revision_path(rev_path);
203 data rev_data;
204 MM(rev_data);
205 try
206 {
207 read_data(rev_path, rev_data);
208 }
209 catch(exception & e)
210 {
211 E(false, F("workspace is corrupt: reading %s: %s")
212 % rev_path % e.what());
213 }
214
215 read_revision(rev_data, rev);
216 // Mark it so it doesn't creep into the database.
217 rev.made_for = made_for_workspace;
218}
219
220void
221workspace::put_work_rev(revision_t const & rev)
222{
223 MM(rev);
224 I(rev.made_for == made_for_workspace);
225 rev.check_sane();
226
227 data rev_data;
228 write_revision(rev, rev_data);
229
230 bookkeeping_path rev_path;
231 get_revision_path(rev_path);
232 write_data(rev_path, rev_data);
233}
234
235// structures derived from the work revision, the database, and possibly
236// the workspace
237
238static void
239get_roster_for_rid(database & db,
240 revision_id const & rid,
241 cached_roster & cr)
242{
243 // We may be asked for a roster corresponding to the null rid, which
244 // is not in the database. In this situation, what is wanted is an empty
245 // roster (and marking map).
246 if (null_id(rid))
247 {
248 cr.first = boost::shared_ptr<roster_t const>(new roster_t);
249 cr.second = boost::shared_ptr<marking_map const>(new marking_map);
250 }
251 else
252 {
253 N(db.revision_exists(rid),
254 F("base revision %s does not exist in database") % rid);
255 db.get_roster(rid, cr);
256 }
257 L(FL("base roster has %d entries") % cr.first->all_nodes().size());
258}
259
260void
261workspace::get_parent_rosters(database & db, parent_map & parents)
262{
263 revision_t rev;
264 get_work_rev(rev);
265
266 parents.clear();
267 for (edge_map::const_iterator i = rev.edges.begin();
268 i != rev.edges.end(); i++)
269 {
270 cached_roster cr;
271 get_roster_for_rid(db, edge_old_revision(i), cr);
272 safe_insert(parents, make_pair(edge_old_revision(i), cr));
273 }
274}
275
276void
277workspace::get_current_roster_shape(database & db,
278 node_id_source & nis,
279 roster_t & ros)
280{
281 revision_t rev;
282 get_work_rev(rev);
283 revision_id new_rid(fake_id());
284
285 // If there is just one parent, it might be the null ID, which
286 // make_roster_for_revision does not handle correctly.
287 if (rev.edges.size() == 1 && null_id(edge_old_revision(rev.edges.begin())))
288 {
289 I(ros.all_nodes().empty());
290 editable_roster_base er(ros, nis);
291 edge_changes(rev.edges.begin()).apply_to(er);
292 }
293 else
294 {
295 marking_map dummy;
296 make_roster_for_revision(db, nis, rev, new_rid, ros, dummy);
297 }
298}
299
300bool
301workspace::has_changes(database & db)
302{
303 parent_map parents;
304 get_parent_rosters(db, parents);
305
306 // if we have more than one parent roster then this workspace contains
307 // a merge which means this is always a committable change
308 if (parents.size() > 1)
309 return true;
310
311 temp_node_id_source nis;
312 roster_t new_roster, old_roster = parent_roster(parents.begin());
313
314 get_current_roster_shape(db, nis, new_roster);
315 update_current_roster_from_filesystem(new_roster);
316
317 return !(old_roster == new_roster);
318}
319
320// user log file
321
322void
323workspace::read_user_log(utf8 & dat)
324{
325 bookkeeping_path ul_path;
326 get_user_log_path(ul_path);
327
328 if (file_exists(ul_path))
329 {
330 data tmp;
331 read_data(ul_path, tmp);
332 system_to_utf8(external(tmp()), dat);
333 }
334}
335
336void
337workspace::write_user_log(utf8 const & dat)
338{
339 bookkeeping_path ul_path;
340 get_user_log_path(ul_path);
341
342 external tmp;
343 utf8_to_system_best_effort(dat, tmp);
344 write_data(ul_path, data(tmp()));
345}
346
347void
348workspace::blank_user_log()
349{
350 data empty;
351 bookkeeping_path ul_path;
352 get_user_log_path(ul_path);
353 write_data(ul_path, empty);
354}
355
356bool
357workspace::has_contents_user_log()
358{
359 utf8 user_log_message;
360 read_user_log(user_log_message);
361 return user_log_message().length() > 0;
362}
363
364// _MTN/options handling.
365
366static void
367read_options_file(any_path const & optspath,
368 system_path & database_option,
369 branch_name & branch_option,
370 rsa_keypair_id & key_option,
371 system_path & keydir_option)
372{
373 data dat;
374 try
375 {
376 read_data(optspath, dat);
377 }
378 catch (exception & e)
379 {
380 W(F("Failed to read options file %s: %s") % optspath % e.what());
381 return;
382 }
383
384 basic_io::input_source src(dat(), optspath.as_external());
385 basic_io::tokenizer tok(src);
386 basic_io::parser parser(tok);
387
388 while (parser.symp())
389 {
390 string opt, val;
391 parser.sym(opt);
392 parser.str(val);
393
394 if (opt == "database")
395 database_option = system_path(val);
396 else if (opt == "branch")
397 branch_option = branch_name(val);
398 else if (opt == "key")
399 internalize_rsa_keypair_id(utf8(val), key_option);
400 else if (opt == "keydir")
401 keydir_option = system_path(val);
402 else
403 W(F("unrecognized key '%s' in options file %s - ignored")
404 % opt % optspath);
405 }
406 E(src.lookahead == EOF,
407 F("Could not parse entire options file %s") % optspath);
408}
409
410static void
411write_options_file(bookkeeping_path const & optspath,
412 system_path const & database_option,
413 branch_name const & branch_option,
414 rsa_keypair_id const & key_option,
415 system_path const & keydir_option)
416{
417 basic_io::stanza st;
418 if (!database_option.as_internal().empty())
419 st.push_str_pair(symbol("database"), database_option.as_internal());
420 if (!branch_option().empty())
421 st.push_str_pair(symbol("branch"), branch_option());
422 if (!key_option().empty())
423 {
424 utf8 key;
425 externalize_rsa_keypair_id(key_option, key);
426 st.push_str_pair(symbol("key"), key());
427 }
428 if (!keydir_option.as_internal().empty())
429 st.push_str_pair(symbol("keydir"), keydir_option.as_internal());
430
431 basic_io::printer pr;
432 pr.print_stanza(st);
433 try
434 {
435 write_data(optspath, data(pr.buf));
436 }
437 catch(exception & e)
438 {
439 W(F("Failed to write options file %s: %s") % optspath % e.what());
440 }
441}
442
443void
444workspace::get_ws_options(options & opts)
445{
446 if (!workspace::found)
447 return;
448
449 system_path database_option;
450 branch_name branch_option;
451 rsa_keypair_id key_option;
452 system_path keydir_option;
453
454 bookkeeping_path o_path;
455 get_options_path(o_path);
456 read_options_file(o_path,
457 database_option, branch_option, key_option, keydir_option);
458
459 // Workspace options are not to override the command line.
460 if (!opts.dbname_given)
461 {
462 opts.dbname = database_option;
463 }
464
465 if (!opts.key_dir_given && !opts.conf_dir_given)
466 {
467 opts.key_dir = keydir_option;
468 opts.key_dir_given = true;
469 }
470
471 if (opts.branchname().empty() && !branch_option().empty())
472 {
473 opts.branchname = branch_option;
474 branch_is_sticky = true;
475 }
476
477 L(FL("branch name is '%s'") % opts.branchname);
478
479 if (!opts.key_given)
480 opts.signing_key = key_option;
481}
482
483void
484workspace::get_database_option(system_path const & workspace,
485 system_path & database_option)
486{
487 branch_name branch_option;
488 rsa_keypair_id key_option;
489 system_path keydir_option;
490
491 system_path o_path = (workspace
492 / bookkeeping_root_component
493 / options_file_name);
494 read_options_file(o_path,
495 database_option, branch_option, key_option, keydir_option);
496}
497
498void
499workspace::set_ws_options(options const & opts, bool branch_is_sticky)
500{
501 N(workspace::found, F("workspace required but not found"));
502
503 bookkeeping_path o_path;
504 get_options_path(o_path);
505
506 // If any of the incoming options was empty, we want to leave that option
507 // as is in _MTN/options, not write out an empty option.
508 system_path database_option;
509 branch_name branch_option;
510 rsa_keypair_id key_option;
511 system_path keydir_option;
512
513 if (file_exists(o_path))
514 read_options_file(o_path,
515 database_option, branch_option, key_option, keydir_option);
516
517 // FIXME: we should do more checks here, f.e. if this is a valid sqlite
518 // file and if it contains the correct identifier, but these checks would
519 // duplicate those in database.cc. At the time it is checked there, however,
520 // the options file for the workspace is already written out...
521 if (!opts.dbname.as_internal().empty() &&
522 get_path_status(opts.dbname.as_internal()) == path::file)
523 database_option = opts.dbname;
524 if (!opts.key_dir.as_internal().empty() &&
525 get_path_status(opts.key_dir.as_internal()) == path::directory)
526 keydir_option = opts.key_dir;
527 if ((branch_is_sticky || workspace::branch_is_sticky)
528 && !opts.branchname().empty())
529 branch_option = opts.branchname;
530 if (opts.key_given)
531 key_option = opts.signing_key;
532
533 write_options_file(o_path,
534 database_option, branch_option, key_option, keydir_option);
535}
536
537void
538workspace::print_ws_option(utf8 const & opt, std::ostream & output)
539{
540 N(workspace::found, F("workspace required but not found"));
541
542 bookkeeping_path o_path;
543 get_options_path(o_path);
544
545 system_path database_option;
546 branch_name branch_option;
547 rsa_keypair_id key_option;
548 system_path keydir_option;
549 read_options_file(o_path,
550 database_option, branch_option, key_option, keydir_option);
551
552 if (opt() == "database")
553 output << database_option << '\n';
554 else if (opt() == "branch")
555 output << branch_option << '\n';
556 else if (opt() == "key")
557 output << key_option << '\n';
558 else if (opt() == "keydir")
559 output << keydir_option << '\n';
560 else
561 N(false, F("'%s' is not a recognized workspace option") % opt);
562}
563
564// local dump file
565
566void
567workspace::get_local_dump_path(bookkeeping_path & d_path)
568{
569 N(workspace::found, F("workspace required but not found"));
570
571 d_path = bookkeeping_root / local_dump_file_name;
572 L(FL("local dump path is %s") % d_path);
573}
574
575// inodeprint file
576
577bool
578workspace::in_inodeprints_mode()
579{
580 bookkeeping_path ip_path;
581 get_inodeprints_path(ip_path);
582 return file_exists(ip_path);
583}
584
585void
586workspace::read_inodeprints(data & dat)
587{
588 I(in_inodeprints_mode());
589 bookkeeping_path ip_path;
590 get_inodeprints_path(ip_path);
591 read_data(ip_path, dat);
592}
593
594void
595workspace::write_inodeprints(data const & dat)
596{
597 I(in_inodeprints_mode());
598 bookkeeping_path ip_path;
599 get_inodeprints_path(ip_path);
600 write_data(ip_path, dat);
601}
602
603void
604workspace::enable_inodeprints()
605{
606 bookkeeping_path ip_path;
607 get_inodeprints_path(ip_path);
608 data dat;
609 write_data(ip_path, dat);
610}
611
612void
613workspace::maybe_update_inodeprints(database & db)
614{
615 if (!in_inodeprints_mode())
616 return;
617
618 inodeprint_map ipm_new;
619 temp_node_id_source nis;
620 roster_t new_roster;
621
622 get_current_roster_shape(db, nis, new_roster);
623 update_current_roster_from_filesystem(new_roster);
624
625 parent_map parents;
626 get_parent_rosters(db, parents);
627
628 node_map const & new_nodes = new_roster.all_nodes();
629 for (node_map::const_iterator i = new_nodes.begin(); i != new_nodes.end(); ++i)
630 {
631 node_id nid = i->first;
632 if (!is_file_t(i->second))
633 continue;
634 file_t new_file = downcast_to_file_t(i->second);
635 bool all_same = true;
636
637 for (parent_map::const_iterator parent = parents.begin();
638 parent != parents.end(); ++parent)
639 {
640 roster_t const & parent_ros = parent_roster(parent);
641 if (parent_ros.has_node(nid))
642 {
643 node_t old_node = parent_ros.get_node(nid);
644 I(is_file_t(old_node));
645 file_t old_file = downcast_to_file_t(old_node);
646
647 if (new_file->content != old_file->content)
648 {
649 all_same = false;
650 break;
651 }
652 }
653 }
654
655 if (all_same)
656 {
657 file_path fp;
658 new_roster.get_name(nid, fp);
659 hexenc<inodeprint> ip;
660 if (inodeprint_file(fp, ip))
661 ipm_new.insert(inodeprint_entry(fp, ip));
662 }
663 }
664 data dat;
665 write_inodeprint_map(ipm_new, dat);
666 write_inodeprints(dat);
667}
668
669bool
670workspace::ignore_file(file_path const & path)
671{
672 return lua.hook_ignore_file(path);
673}
674
675void
676workspace::init_attributes(file_path const & path, editable_roster_base & er)
677{
678 map<string, string> attrs;
679 lua.hook_init_attributes(path, attrs);
680 if (!attrs.empty())
681 for (map<string, string>::const_iterator i = attrs.begin();
682 i != attrs.end(); ++i)
683 er.set_attr(path, attr_key(i->first), attr_value(i->second));
684}
685
686// objects and routines for manipulating the workspace itself
687namespace {
688
689struct file_itemizer : public tree_walker
690{
691 database & db;
692 workspace & work;
693 set<file_path> & known;
694 set<file_path> & unknown;
695 set<file_path> & ignored;
696 path_restriction const & mask;
697 file_itemizer(database & db, workspace & work,
698 set<file_path> & k,
699 set<file_path> & u,
700 set<file_path> & i,
701 path_restriction const & r)
702 : db(db), work(work), known(k), unknown(u), ignored(i), mask(r) {}
703 virtual bool visit_dir(file_path const & path);
704 virtual void visit_file(file_path const & path);
705};
706
707
708bool
709file_itemizer::visit_dir(file_path const & path)
710{
711 this->visit_file(path);
712 return known.find(path) != known.end();
713}
714
715void
716file_itemizer::visit_file(file_path const & path)
717{
718 if (mask.includes(path) && known.find(path) == known.end())
719 {
720 if (work.ignore_file(path) || db.is_dbfile(path))
721 ignored.insert(path);
722 else
723 unknown.insert(path);
724 }
725}
726
727
728struct workspace_itemizer : public tree_walker
729{
730 roster_t & roster;
731 set<file_path> const & known;
732 node_id_source & nis;
733
734 workspace_itemizer(roster_t & roster, set<file_path> const & paths,
735 node_id_source & nis);
736 virtual bool visit_dir(file_path const & path);
737 virtual void visit_file(file_path const & path);
738};
739
740workspace_itemizer::workspace_itemizer(roster_t & roster,
741 set<file_path> const & paths,
742 node_id_source & nis)
743 : roster(roster), known(paths), nis(nis)
744{
745 node_id root_nid = roster.create_dir_node(nis);
746 roster.attach_node(root_nid, file_path_internal(""));
747}
748
749bool
750workspace_itemizer::visit_dir(file_path const & path)
751{
752 node_id nid = roster.create_dir_node(nis);
753 roster.attach_node(nid, path);
754 return known.find(path) != known.end();
755}
756
757void
758workspace_itemizer::visit_file(file_path const & path)
759{
760 file_id fid;
761 node_id nid = roster.create_file_node(fid, nis);
762 roster.attach_node(nid, path);
763}
764
765
766class
767addition_builder
768 : public tree_walker
769{
770 database & db;
771 workspace & work;
772 roster_t & ros;
773 editable_roster_base & er;
774 bool respect_ignore;
775 bool recursive;
776public:
777 addition_builder(database & db, workspace & work,
778 roster_t & r, editable_roster_base & e,
779 bool i, bool rec)
780 : db(db), work(work), ros(r), er(e), respect_ignore(i), recursive(rec)
781 {}
782 virtual bool visit_dir(file_path const & path);
783 virtual void visit_file(file_path const & path);
784 void add_nodes_for(file_path const & path, file_path const & goal);
785};
786
787void
788addition_builder::add_nodes_for(file_path const & path,
789 file_path const & goal)
790{
791 // this check suffices to terminate the recursion; our caller guarantees
792 // that the roster has a root node, which will be a directory.
793 if (ros.has_node(path))
794 {
795 N(is_dir_t(ros.get_node(path)),
796 F("cannot add %s, because %s is recorded as a file "
797 "in the workspace manifest") % goal % path);
798 return;
799 }
800
801 add_nodes_for(path.dirname(), goal);
802 P(F("adding %s to workspace manifest") % path);
803
804 node_id nid = the_null_node;
805 switch (get_path_status(path))
806 {
807 case path::nonexistent:
808 return;
809 case path::file:
810 {
811 file_id ident;
812 worker_pool pool; // FIXME!
813 shared_ptr<file_path> fpath(new file_path(path));
814 shared_ptr<file_id> fid(new file_id());
815 I(ident_existing_file(pool, fpath, fid));
816 pool.wait();
817 ident = *fid;
818 nid = er.create_file_node(ident);
819 }
820 break;
821 case path::directory:
822 nid = er.create_dir_node();
823 break;
824 }
825
826 I(nid != the_null_node);
827 er.attach_node(nid, path);
828
829 work.init_attributes(path, er);
830}
831
832
833bool
834addition_builder::visit_dir(file_path const & path)
835{
836 if (!recursive) {
837 bool warn = false;
838
839 // If the db can ever be stored in a dir
840 // then revisit this logic
841 I(!db.is_dbfile(path));
842
843 if (!respect_ignore)
844 warn = !directory_empty(path);
845 else if (respect_ignore && !work.ignore_file(path))
846 {
847 vector<path_component> children;
848 read_directory(path, children, children);
849
850 for (vector<path_component>::const_iterator i = children.begin();
851 i != children.end(); ++i)
852 {
853 try
854 {
855 file_path entry = path / *i;
856 if (!work.ignore_file(entry) && !db.is_dbfile(entry))
857 {
858 warn = true;
859 break;
860 }
861 }
862 catch (std::logic_error)
863 {
864 // ignore this file for purposes of the warning
865 // this file wouldn't have been added by a
866 // recursive add anyway.
867 }
868 }
869 }
870
871 if (warn)
872 W(F("Non-recursive add: Files in the directory '%s' "
873 "will not be added automatically.") % path);
874 }
875
876 this->visit_file(path);
877 return true;
878}
879
880void
881addition_builder::visit_file(file_path const & path)
882{
883 if ((respect_ignore && work.ignore_file(path)) || db.is_dbfile(path))
884 {
885 P(F("skipping ignorable file %s") % path);
886 return;
887 }
888
889 if (ros.has_node(path))
890 {
891 if (!path.empty())
892 P(F("skipping %s, already accounted for in workspace") % path);
893 return;
894 }
895
896 I(ros.has_root());
897 add_nodes_for(path, path);
898}
899
900struct editable_working_tree : public editable_tree
901{
902 editable_working_tree(workspace & work, content_merge_adaptor const & source,
903 bool const messages)
904 : work(work), source(source), next_nid(1), root_dir_attached(true),
905 messages(messages)
906 {};
907
908 virtual node_id detach_node(file_path const & src);
909 virtual void drop_detached_node(node_id nid);
910
911 virtual node_id create_dir_node();
912 virtual node_id create_file_node(file_id const & content);
913 virtual void attach_node(node_id nid, file_path const & dst);
914
915 virtual void apply_delta(file_path const & pth,
916 file_id const & old_id,
917 file_id const & new_id);
918 virtual void clear_attr(file_path const & pth,
919 attr_key const & name);
920 virtual void set_attr(file_path const & pth,
921 attr_key const & name,
922 attr_value const & val);
923
924 virtual void commit();
925
926 virtual ~editable_working_tree();
927private:
928 workspace & work;
929 content_merge_adaptor const & source;
930 node_id next_nid;
931 std::map<bookkeeping_path, file_path> rename_add_drop_map;
932 bool root_dir_attached;
933 bool messages;
934};
935
936
937struct simulated_working_tree : public editable_tree
938{
939 roster_t & workspace;
940 node_id_source & nis;
941
942 set<file_path> blocked_paths;
943 map<node_id, file_path> nid_map;
944 int conflicts;
945
946 simulated_working_tree(roster_t & r, temp_node_id_source & n)
947 : workspace(r), nis(n), conflicts(0) {}
948
949 virtual node_id detach_node(file_path const & src);
950 virtual void drop_detached_node(node_id nid);
951
952 virtual node_id create_dir_node();
953 virtual node_id create_file_node(file_id const & content);
954 virtual void attach_node(node_id nid, file_path const & dst);
955
956 virtual void apply_delta(file_path const & pth,
957 file_id const & old_id,
958 file_id const & new_id);
959 virtual void clear_attr(file_path const & pth,
960 attr_key const & name);
961 virtual void set_attr(file_path const & pth,
962 attr_key const & name,
963 attr_value const & val);
964
965 virtual void commit();
966
967 virtual ~simulated_working_tree();
968};
969
970
971struct content_merge_empty_adaptor : public content_merge_adaptor
972{
973 virtual void get_version(file_id const &, file_data &) const
974 { I(false); }
975 virtual void record_merge(file_id const &, file_id const &,
976 file_id const &,
977 file_data const &, file_data const &,
978 file_data const &)
979 { I(false); }
980 virtual void get_ancestral_roster(node_id, revision_id &, boost::shared_ptr<roster_t const> &)
981 { I(false); }
982};
983
984// editable_working_tree implementation
985
986static inline bookkeeping_path
987path_for_detached_nids()
988{
989 return bookkeeping_root / "detached";
990}
991
992static inline bookkeeping_path
993path_for_detached_nid(node_id nid)
994{
995 return path_for_detached_nids() / path_component(lexical_cast<string>(nid));
996}
997
998// Attaching/detaching the root directory:
999// This is tricky, because we don't want to simply move it around, like
1000// other directories. That would require some very snazzy handling of the
1001// _MTN directory, and never be possible on windows anyway[1]. So, what we do
1002// is fake it -- whenever we want to move the root directory into the
1003// temporary dir, we instead create a new dir in the temporary dir, move
1004// all of the root's contents into this new dir, and make a note that the root
1005// directory is logically non-existent. Whenever we want to move some
1006// directory out of the temporary dir and onto the root directory, we instead
1007// check that the root is logically nonexistent, move its contents, and note
1008// that it exists again.
1009//
1010// [1] Because the root directory is our working directory, and thus locked in
1011// place. We _could_ chdir out, then move _MTN out, then move the real root
1012// directory into our newly-moved _MTN, etc., but aside from being very finicky,
1013// this would require that we know our root directory's name relative to its
1014// parent.
1015
1016node_id
1017editable_working_tree::detach_node(file_path const & src_pth)
1018{
1019 I(root_dir_attached);
1020 node_id nid = next_nid++;
1021 bookkeeping_path dst_pth = path_for_detached_nid(nid);
1022 safe_insert(rename_add_drop_map, make_pair(dst_pth, src_pth));
1023 if (src_pth == file_path())
1024 {
1025 // root dir detach, so we move contents, rather than the dir itself
1026 mkdir_p(dst_pth);
1027 vector<path_component> files, dirs;
1028 read_directory(src_pth, files, dirs);
1029 for (vector<path_component>::const_iterator i = files.begin();
1030 i != files.end(); ++i)
1031 move_file(src_pth / *i, dst_pth / *i);
1032 for (vector<path_component>::const_iterator i = dirs.begin();
1033 i != dirs.end(); ++i)
1034 if (!bookkeeping_path::internal_string_is_bookkeeping_path(utf8((*i)())))
1035 move_dir(src_pth / *i, dst_pth / *i);
1036 root_dir_attached = false;
1037 }
1038 else
1039 move_path(src_pth, dst_pth);
1040 return nid;
1041}
1042
1043void
1044editable_working_tree::drop_detached_node(node_id nid)
1045{
1046 bookkeeping_path pth = path_for_detached_nid(nid);
1047 map<bookkeeping_path, file_path>::const_iterator i
1048 = rename_add_drop_map.find(pth);
1049 I(i != rename_add_drop_map.end());
1050 P(F("dropping %s") % i->second);
1051 safe_erase(rename_add_drop_map, pth);
1052 delete_file_or_dir_shallow(pth);
1053}
1054
1055node_id
1056editable_working_tree::create_dir_node()
1057{
1058 node_id nid = next_nid++;
1059 bookkeeping_path pth = path_for_detached_nid(nid);
1060 require_path_is_nonexistent(pth,
1061 F("path %s already exists") % pth);
1062 mkdir_p(pth);
1063 return nid;
1064}
1065
1066node_id
1067editable_working_tree::create_file_node(file_id const & content)
1068{
1069 node_id nid = next_nid++;
1070 bookkeeping_path pth = path_for_detached_nid(nid);
1071 require_path_is_nonexistent(pth,
1072 F("path %s already exists") % pth);
1073 file_data dat;
1074 source.get_version(content, dat);
1075 write_data(pth, dat.inner());
1076
1077 return nid;
1078}
1079
1080void
1081editable_working_tree::attach_node(node_id nid, file_path const & dst_pth)
1082{
1083 bookkeeping_path src_pth = path_for_detached_nid(nid);
1084
1085 map<bookkeeping_path, file_path>::const_iterator i
1086 = rename_add_drop_map.find(src_pth);
1087 if (i != rename_add_drop_map.end())
1088 {
1089 if (messages)
1090 P(F("renaming %s to %s") % i->second % dst_pth);
1091 safe_erase(rename_add_drop_map, src_pth);
1092 }
1093 else if (messages)
1094 P(F("adding %s") % dst_pth);
1095
1096 if (dst_pth == file_path())
1097 {
1098 // root dir attach, so we move contents, rather than the dir itself
1099 vector<path_component> files, dirs;
1100 read_directory(src_pth, files, dirs);
1101 for (vector<path_component>::const_iterator i = files.begin();
1102 i != files.end(); ++i)
1103 {
1104 I(!bookkeeping_path::internal_string_is_bookkeeping_path(utf8((*i)())));
1105 move_file(src_pth / *i, dst_pth / *i);
1106 }
1107 for (vector<path_component>::const_iterator i = dirs.begin();
1108 i != dirs.end(); ++i)
1109 {
1110 I(!bookkeeping_path::internal_string_is_bookkeeping_path(utf8((*i)())));
1111 move_dir(src_pth / *i, dst_pth / *i);
1112 }
1113 delete_dir_shallow(src_pth);
1114 root_dir_attached = true;
1115 }
1116 else
1117 // This will complain if the move is actually impossible
1118 move_path(src_pth, dst_pth);
1119}
1120
1121void
1122editable_working_tree::apply_delta(file_path const & pth,
1123 file_id const & old_id,
1124 file_id const & new_id)
1125{
1126 require_path_is_file(pth,
1127 F("file '%s' does not exist") % pth,
1128 F("file '%s' is a directory") % pth);
1129 file_id curr_id;
1130 calculate_ident(pth, curr_id);
1131 E(curr_id == old_id,
1132 F("content of file '%s' has changed, not overwriting") % pth);
1133 P(F("modifying %s") % pth);
1134
1135 file_data dat;
1136 source.get_version(new_id, dat);
1137 write_data(pth, dat.inner());
1138}
1139
1140void
1141editable_working_tree::clear_attr(file_path const & pth,
1142 attr_key const & name)
1143{
1144 // FIXME_ROSTERS: call a lua hook
1145}
1146
1147void
1148editable_working_tree::set_attr(file_path const & pth,
1149 attr_key const & name,
1150 attr_value const & val)
1151{
1152 // FIXME_ROSTERS: call a lua hook
1153}
1154
1155void
1156editable_working_tree::commit()
1157{
1158 I(rename_add_drop_map.empty());
1159 I(root_dir_attached);
1160}
1161
1162editable_working_tree::~editable_working_tree()
1163{
1164}
1165
1166
1167node_id
1168simulated_working_tree::detach_node(file_path const & src)
1169{
1170 node_id nid = workspace.detach_node(src);
1171 nid_map.insert(make_pair(nid, src));
1172 return nid;
1173}
1174
1175void
1176simulated_working_tree::drop_detached_node(node_id nid)
1177{
1178 node_t node = workspace.get_node(nid);
1179 if (is_dir_t(node))
1180 {
1181 dir_t dir = downcast_to_dir_t(node);
1182 if (!dir->children.empty())
1183 {
1184 map<node_id, file_path>::const_iterator i = nid_map.find(nid);
1185 I(i != nid_map.end());
1186 W(F("cannot drop non-empty directory '%s'") % i->second);
1187 conflicts++;
1188 }
1189 }
1190}
1191
1192node_id
1193simulated_working_tree::create_dir_node()
1194{
1195 return workspace.create_dir_node(nis);
1196}
1197
1198node_id
1199simulated_working_tree::create_file_node(file_id const & content)
1200{
1201 return workspace.create_file_node(content, nis);
1202}
1203
1204void
1205simulated_working_tree::attach_node(node_id nid, file_path const & dst)
1206{
1207 // this check is needed for checkout because we're using a roster to
1208 // represent paths that *may* block the checkout. however to represent
1209 // these we *must* have a root node in the roster which will *always*
1210 // block us. so here we check for that case and avoid it.
1211 if (dst.empty() && workspace.has_root())
1212 return;
1213
1214 if (workspace.has_node(dst))
1215 {
1216 W(F("attach node %d blocked by unversioned path '%s'") % nid % dst);
1217 blocked_paths.insert(dst);
1218 conflicts++;
1219 }
1220 else if (dst.empty())
1221 {
1222 // the parent of the workspace root cannot be in the blocked set
1223 // this attach would have been caught above if it were a problem
1224 workspace.attach_node(nid, dst);
1225 }
1226 else
1227 {
1228 file_path parent = dst.dirname();
1229
1230 if (blocked_paths.find(parent) == blocked_paths.end())
1231 workspace.attach_node(nid, dst);
1232 else
1233 {
1234 W(F("attach node %d blocked by blocked parent '%s'")
1235 % nid % parent);
1236 blocked_paths.insert(dst);
1237 }
1238 }
1239}
1240
1241void
1242simulated_working_tree::apply_delta(file_path const & path,
1243 file_id const & old_id,
1244 file_id const & new_id)
1245{
1246 // this may fail if path is not a file but that will be caught
1247 // earlier in update_current_roster_from_filesystem
1248}
1249
1250void
1251simulated_working_tree::clear_attr(file_path const & pth,
1252 attr_key const & name)
1253{
1254}
1255
1256void
1257simulated_working_tree::set_attr(file_path const & pth,
1258 attr_key const & name,
1259 attr_value const & val)
1260{
1261}
1262
1263void
1264simulated_working_tree::commit()
1265{
1266 N(conflicts == 0, F("%d workspace conflicts") % conflicts);
1267}
1268
1269simulated_working_tree::~simulated_working_tree()
1270{
1271}
1272
1273
1274}; // anonymous namespace
1275
1276static void
1277add_parent_dirs(database & db, node_id_source & nis, workspace & work,
1278 file_path const & dst, roster_t & ros)
1279{
1280 editable_roster_base er(ros, nis);
1281 addition_builder build(db, work, ros, er, false, true);
1282
1283 // FIXME: this is a somewhat odd way to use the builder
1284 build.visit_dir(dst.dirname());
1285}
1286
1287// updating rosters from the workspace
1288
1289void
1290workspace::update_current_roster_from_filesystem(roster_t & ros)
1291{
1292 update_current_roster_from_filesystem(ros, node_restriction());
1293}
1294
1295class file_hash_calc_task
1296{
1297private:
1298 shared_ptr<file_path> in;
1299 shared_ptr<file_id> out;
1300
1301public:
1302 file_hash_calc_task(shared_ptr<file_path> in,
1303 shared_ptr<file_id> out)
1304 : in(in), out(out)
1305 { };
1306
1307 virtual void operator()()
1308 {
1309 calculate_ident(*in, *out);
1310 }
1311};
1312
1313void NoOpDeallocator(file_id *p)
1314{
1315 /* no-op */
1316}
1317
1318void
1319workspace::update_current_roster_from_filesystem(roster_t & ros,
1320 node_restriction const & mask)
1321{
1322 temp_node_id_source nis;
1323 inodeprint_map ipm;
1324
1325 if (in_inodeprints_mode())
1326 {
1327 data dat;
1328 read_inodeprints(dat);
1329 read_inodeprint_map(dat, ipm);
1330 }
1331
1332 size_t missing_items = 0;
1333
1334 // this code is speed critical, hence the use of inode fingerprints so be
1335 // careful when making changes in here and preferably do some timing tests
1336
1337 if (!ros.has_root())
1338 return;
1339
1340 node_map const & nodes = ros.all_nodes();
1341 worker_pool pool;
1342 for (node_map::const_iterator i = nodes.begin(); i != nodes.end(); ++i)
1343 {
1344 node_id nid = i->first;
1345 node_t node = i->second;
1346
1347 // Only analyze restriction-included files and dirs
1348 if (!mask.includes(ros, nid))
1349 continue;
1350
1351 shared_ptr<file_path> fp(new file_path());
1352 ros.get_name(nid, *fp);
1353
1354 const path::status status(get_path_status(*fp));
1355
1356 if (is_dir_t(node))
1357 {
1358 if (status == path::nonexistent)
1359 {
1360 W(F("missing directory '%s'") % (*fp));
1361 missing_items++;
1362 }
1363 else if (status != path::directory)
1364 {
1365 W(F("not a directory '%s'") % (*fp));
1366 missing_items++;
1367 }
1368 }
1369 else
1370 {
1371 // Only analyze changed files (or all files if inodeprints mode
1372 // is disabled).
1373 if (inodeprint_unchanged(ipm, *fp))
1374 continue;
1375
1376 if (status == path::nonexistent)
1377 {
1378 W(F("missing file '%s'") % (*fp));
1379 missing_items++;
1380 }
1381 else if (status != path::file)
1382 {
1383 W(F("not a file '%s'") % (*fp));
1384 missing_items++;
1385 }
1386
1387 {
1388 file_t file = downcast_to_file_t(node);
1389 shared_ptr<file_id> fid(&file->content, NoOpDeallocator);
1390 ident_existing_file(pool, fp, fid, status);
1391 }
1392 }
1393 }
1394
1395 // wait until all jobs in the pool are done.
1396 pool.wait();
1397
1398 N(missing_items == 0,
1399 F("%d missing items; use '%s ls missing' to view\n"
1400 "To restore consistency, on each missing item run either\n"
1401 " '%s drop ITEM' to remove it permanently, or\n"
1402 " '%s revert ITEM' to restore it.\n"
1403 "To handle all at once, simply use\n"
1404 " '%s drop --missing' or\n"
1405 " '%s revert --missing'")
1406 % missing_items % ui.prog_name % ui.prog_name % ui.prog_name
1407 % ui.prog_name % ui.prog_name);
1408}
1409
1410void
1411workspace::find_missing(roster_t const & new_roster_shape,
1412 node_restriction const & mask,
1413 set<file_path> & missing)
1414{
1415 node_map const & nodes = new_roster_shape.all_nodes();
1416 for (node_map::const_iterator i = nodes.begin(); i != nodes.end(); ++i)
1417 {
1418 node_id nid = i->first;
1419
1420 if (!new_roster_shape.is_root(nid)
1421 && mask.includes(new_roster_shape, nid))
1422 {
1423 file_path fp;
1424 new_roster_shape.get_name(nid, fp);
1425 if (!path_exists(fp))
1426 missing.insert(fp);
1427 }
1428 }
1429}
1430
1431void
1432workspace::find_unknown_and_ignored(database & db,
1433 path_restriction const & mask,
1434 vector<file_path> const & roots,
1435 set<file_path> & unknown,
1436 set<file_path> & ignored)
1437{
1438 set<file_path> known;
1439 roster_t new_roster;
1440 temp_node_id_source nis;
1441
1442 get_current_roster_shape(db, nis, new_roster);
1443 new_roster.extract_path_set(known);
1444
1445 file_itemizer u(db, *this, known, unknown, ignored, mask);
1446 for (vector<file_path>::const_iterator
1447 i = roots.begin(); i != roots.end(); ++i)
1448 {
1449 walk_tree(*i, u);
1450 }
1451}
1452
1453void
1454workspace::perform_additions(database & db, set<file_path> const & paths,
1455 bool recursive, bool respect_ignore)
1456{
1457 if (paths.empty())
1458 return;
1459
1460 temp_node_id_source nis;
1461 roster_t new_roster;
1462 MM(new_roster);
1463 get_current_roster_shape(db, nis, new_roster);
1464
1465 editable_roster_base er(new_roster, nis);
1466
1467 if (!new_roster.has_root())
1468 {
1469 er.attach_node(er.create_dir_node(), file_path_internal(""));
1470 }
1471
1472 I(new_roster.has_root());
1473 addition_builder build(db, *this, new_roster, er, respect_ignore, recursive);
1474
1475 for (set<file_path>::const_iterator i = paths.begin(); i != paths.end(); ++i)
1476 {
1477 if (recursive)
1478 {
1479 // NB.: walk_tree will handle error checking for non-existent paths
1480 walk_tree(*i, build);
1481 }
1482 else
1483 {
1484 // in the case where we're just handed a set of paths, we use the
1485 // builder in this strange way.
1486 switch (get_path_status(*i))
1487 {
1488 case path::nonexistent:
1489 N(false, F("no such file or directory: '%s'") % *i);
1490 break;
1491 case path::file:
1492 build.visit_file(*i);
1493 break;
1494 case path::directory:
1495 build.visit_dir(*i);
1496 break;
1497 }
1498 }
1499 }
1500
1501 parent_map parents;
1502 get_parent_rosters(db, parents);
1503
1504 revision_t new_work;
1505 make_revision_for_workspace(parents, new_roster, new_work);
1506 put_work_rev(new_work);
1507 update_any_attrs(db);
1508}
1509
1510static bool
1511in_parent_roster(const parent_map & parents, const node_id & nid)
1512{
1513 for (parent_map::const_iterator i = parents.begin();
1514 i != parents.end();
1515 i++)
1516 {
1517 if (parent_roster(i).has_node(nid))
1518 return true;
1519 }
1520
1521 return false;
1522}
1523
1524void
1525workspace::perform_deletions(database & db,
1526 set<file_path> const & paths,
1527 bool recursive, bool bookkeep_only)
1528{
1529 if (paths.empty())
1530 return;
1531
1532 temp_node_id_source nis;
1533 roster_t new_roster;
1534 MM(new_roster);
1535 get_current_roster_shape(db, nis, new_roster);
1536
1537 parent_map parents;
1538 get_parent_rosters(db, parents);
1539
1540 // we traverse the the paths backwards, so that we always hit deep paths
1541 // before shallow paths (because set<file_path> is lexicographically
1542 // sorted). this is important in cases like
1543 // monotone drop foo/bar foo foo/baz
1544 // where, when processing 'foo', we need to know whether or not it is empty
1545 // (and thus legal to remove)
1546
1547 deque<file_path> todo;
1548 set<file_path>::const_reverse_iterator i = paths.rbegin();
1549 todo.push_back(*i);
1550 ++i;
1551
1552 while (todo.size())
1553 {
1554 file_path const & name(todo.front());
1555
1556 E(!name.empty(),
1557 F("unable to drop the root directory"));
1558
1559 if (!new_roster.has_node(name))
1560 P(F("skipping %s, not currently tracked") % name);
1561 else
1562 {
1563 node_t n = new_roster.get_node(name);
1564 if (is_dir_t(n))
1565 {
1566 dir_t d = downcast_to_dir_t(n);
1567 if (!d->children.empty())
1568 {
1569 N(recursive,
1570 F("cannot remove %s/, it is not empty") % name);
1571 for (dir_map::const_iterator j = d->children.begin();
1572 j != d->children.end(); ++j)
1573 todo.push_front(name / j->first);
1574 continue;
1575 }
1576 }
1577 if (!bookkeep_only && path_exists(name)
1578 && in_parent_roster(parents, n->self))
1579 {
1580 if (is_dir_t(n))
1581 {
1582 if (directory_empty(name))
1583 delete_file_or_dir_shallow(name);
1584 else
1585 W(F("directory %s not empty - "
1586 "it will be dropped but not deleted") % name);
1587 }
1588 else
1589 {
1590 file_t file = downcast_to_file_t(n);
1591 shared_ptr<file_path> fpath(new file_path(name));
1592 shared_ptr<file_id> fid(new file_id());
1593 worker_pool pool; // FIXME: speed this up!
1594 I(ident_existing_file(pool, fpath, fid));
1595 pool.wait();
1596 if (file->content == *fid)
1597 delete_file_or_dir_shallow(name);
1598 else
1599 W(F("file %s changed - "
1600 "it will be dropped but not deleted") % name);
1601 }
1602 }
1603 P(F("dropping %s from workspace manifest") % name);
1604 new_roster.drop_detached_node(new_roster.detach_node(name));
1605 }
1606 todo.pop_front();
1607 if (i != paths.rend())
1608 {
1609 todo.push_back(*i);
1610 ++i;
1611 }
1612 }
1613
1614 revision_t new_work;
1615 make_revision_for_workspace(parents, new_roster, new_work);
1616 put_work_rev(new_work);
1617 update_any_attrs(db);
1618}
1619
1620void
1621workspace::perform_rename(database & db,
1622 set<file_path> const & srcs,
1623 file_path const & dst,
1624 bool bookkeep_only)
1625{
1626 temp_node_id_source nis;
1627 roster_t new_roster;
1628 MM(new_roster);
1629 set< pair<file_path, file_path> > renames;
1630
1631 I(!srcs.empty());
1632
1633 get_current_roster_shape(db, nis, new_roster);
1634
1635 // validation. it's okay if the target exists as a file; we just won't
1636 // clobber it (in !--bookkeep-only mode). similarly, it's okay if the
1637 // source does not exist as a file.
1638 if (srcs.size() == 1 && !new_roster.has_node(dst))
1639 {
1640 // "rename SRC DST" case
1641 file_path const & src = *srcs.begin();
1642 file_path dpath = dst;
1643
1644 N(!src.empty(),
1645 F("cannot rename the workspace root (try '%s pivot_root' instead)")
1646 % ui.prog_name);
1647 N(new_roster.has_node(src),
1648 F("source file %s is not versioned") % src);
1649
1650 //this allows the 'magic add' of a non-versioned directory to happen in
1651 //all cases. previously, mtn mv fileA dir/ woudl fail if dir/ wasn't
1652 //versioned whereas mtn mv fileA dir/fileA would add dir/ if necessary
1653 //and then reparent fileA.
1654 if (get_path_status(dst) == path::directory)
1655 dpath = dst / src.basename();
1656 else
1657 {
1658 //this handles the case where:
1659 // touch foo
1660 // mtn mv foo bar/foo where bar doesn't exist
1661 file_path parent = dst.dirname();
1662 N(get_path_status(parent) == path::directory,
1663 F("destination path's parent directory %s/ doesn't exist") % parent);
1664 }
1665
1666 renames.insert(make_pair(src, dpath));
1667 add_parent_dirs(db, nis, *this, dpath, new_roster);
1668 }
1669 else
1670 {
1671 // "rename SRC1 [SRC2 ...] DSTDIR" case
1672 N(get_path_status(dst) == path::directory,
1673 F("destination %s/ is not a directory") % dst);
1674
1675 for (set<file_path>::const_iterator i = srcs.begin();
1676 i != srcs.end(); i++)
1677 {
1678 N(!i->empty(),
1679 F("cannot rename the workspace root (try '%s pivot_root' instead)")
1680 % ui.prog_name);
1681 N(new_roster.has_node(*i),
1682 F("source file %s is not versioned") % *i);
1683
1684 file_path d = dst / i->basename();
1685 N(!new_roster.has_node(d),
1686 F("destination %s already exists in the workspace manifest") % d);
1687
1688 renames.insert(make_pair(*i, d));
1689
1690 add_parent_dirs(db, nis, *this, d, new_roster);
1691 }
1692 }
1693
1694 // do the attach/detaching
1695 for (set< pair<file_path, file_path> >::const_iterator i = renames.begin();
1696 i != renames.end(); i++)
1697 {
1698 node_id nid = new_roster.detach_node(i->first);
1699 new_roster.attach_node(nid, i->second);
1700 P(F("renaming %s to %s in workspace manifest") % i->first % i->second);
1701 }
1702
1703 parent_map parents;
1704 get_parent_rosters(db, parents);
1705
1706 revision_t new_work;
1707 make_revision_for_workspace(parents, new_roster, new_work);
1708 put_work_rev(new_work);
1709
1710 if (!bookkeep_only)
1711 for (set< pair<file_path, file_path> >::const_iterator i = renames.begin();
1712 i != renames.end(); i++)
1713 {
1714 file_path const & s(i->first);
1715 file_path const & d(i->second);
1716 // silently skip files where src doesn't exist or dst does
1717 bool have_src = path_exists(s);
1718 bool have_dst = path_exists(d);
1719 if (have_src && !have_dst)
1720 {
1721 move_path(s, d);
1722 }
1723 else if (!have_src && !have_dst)
1724 {
1725 W(F("%s doesn't exist in workspace, skipping") % s);
1726 }
1727 else if (have_src && have_dst)
1728 {
1729 W(F("destination %s already exists in workspace, "
1730 "skipping filesystem rename") % d);
1731 }
1732 else
1733 {
1734 W(F("%s doesn't exist in workspace and %s does, "
1735 "skipping filesystem rename") % s % d);
1736 }
1737 }
1738
1739 update_any_attrs(db);
1740}
1741
1742void
1743workspace::perform_pivot_root(database & db,
1744 file_path const & new_root,
1745 file_path const & put_old,
1746 bool bookkeep_only)
1747{
1748 temp_node_id_source nis;
1749 roster_t new_roster;
1750 MM(new_roster);
1751 get_current_roster_shape(db, nis, new_roster);
1752
1753 I(new_roster.has_root());
1754 N(new_roster.has_node(new_root),
1755 F("proposed new root directory '%s' is not versioned or does not exist")
1756 % new_root);
1757 N(is_dir_t(new_roster.get_node(new_root)),
1758 F("proposed new root directory '%s' is not a directory") % new_root);
1759 {
1760 N(!new_roster.has_node(new_root / bookkeeping_root_component),
1761 F("proposed new root directory '%s' contains illegal path %s")
1762 % new_root % bookkeeping_root);
1763 }
1764
1765 {
1766 file_path current_path_to_put_old = (new_root / put_old);
1767 file_path current_path_to_put_old_parent
1768 = current_path_to_put_old.dirname();
1769
1770 N(new_roster.has_node(current_path_to_put_old_parent),
1771 F("directory '%s' is not versioned or does not exist")
1772 % current_path_to_put_old_parent);
1773 N(is_dir_t(new_roster.get_node(current_path_to_put_old_parent)),
1774 F("'%s' is not a directory")
1775 % current_path_to_put_old_parent);
1776 N(!new_roster.has_node(current_path_to_put_old),
1777 F("'%s' is in the way") % current_path_to_put_old);
1778 }
1779
1780 cset cs;
1781 safe_insert(cs.nodes_renamed, make_pair(file_path_internal(""), put_old));
1782 safe_insert(cs.nodes_renamed, make_pair(new_root, file_path_internal("")));
1783
1784 {
1785 editable_roster_base e(new_roster, nis);
1786 cs.apply_to(e);
1787 }
1788
1789 {
1790 parent_map parents;
1791 get_parent_rosters(db, parents);
1792
1793 revision_t new_work;
1794 make_revision_for_workspace(parents, new_roster, new_work);
1795 put_work_rev(new_work);
1796 }
1797 if (!bookkeep_only)
1798 {
1799 content_merge_empty_adaptor cmea;
1800 perform_content_update(db, cs, cmea);
1801 }
1802 update_any_attrs(db);
1803}
1804
1805void
1806workspace::perform_content_update(database & db,
1807 cset const & update,
1808 content_merge_adaptor const & ca,
1809 bool const messages)
1810{
1811 roster_t roster;
1812 temp_node_id_source nis;
1813 set<file_path> known;
1814 roster_t new_roster;
1815 bookkeeping_path detached = path_for_detached_nids();
1816
1817 E(!directory_exists(detached),
1818 F("workspace is locked\n"
1819 "you must clean up and remove the %s directory")
1820 % detached);
1821
1822 get_current_roster_shape(db, nis, new_roster);
1823 new_roster.extract_path_set(known);
1824
1825 workspace_itemizer itemizer(roster, known, nis);
1826 walk_tree(file_path(), itemizer);
1827
1828 simulated_working_tree swt(roster, nis);
1829 update.apply_to(swt);
1830
1831 mkdir_p(detached);
1832
1833 editable_working_tree ewt(*this, ca, messages);
1834 update.apply_to(ewt);
1835
1836 delete_dir_shallow(detached);
1837}
1838
1839void
1840workspace::update_any_attrs(database & db)
1841{
1842 temp_node_id_source nis;
1843 roster_t new_roster;
1844 get_current_roster_shape(db, nis, new_roster);
1845 node_map const & nodes = new_roster.all_nodes();
1846 for (node_map::const_iterator i = nodes.begin();
1847 i != nodes.end(); ++i)
1848 {
1849 file_path fp;
1850 new_roster.get_name(i->first, fp);
1851
1852 node_t n = i->second;
1853 for (full_attr_map_t::const_iterator j = n->attrs.begin();
1854 j != n->attrs.end(); ++j)
1855 if (j->second.first)
1856 lua.hook_apply_attribute (j->first(), fp,
1857 j->second.second());
1858 }
1859}
1860
1861// Local Variables:
1862// mode: C++
1863// fill-column: 76
1864// c-file-style: "gnu"
1865// indent-tabs-mode: nil
1866// End:
1867// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

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