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