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