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