monotone

monotone Mtn Source Tree

Root/work.cc

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