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