monotone

monotone Mtn Source Tree

Root/work.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include <sstream>
11#include <cstdio>
12#include <cstring>
13#include <cerrno>
14#include <queue>
15
16#include "app_state.hh"
17#include "basic_io.hh"
18#include "cset.hh"
19#include "localized_file_io.hh"
20#include "platform.hh"
21#include "restrictions.hh"
22#include "sanity.hh"
23#include "safe_map.hh"
24#include "simplestring_xform.hh"
25#include "vocab.hh"
26#include "work.hh"
27#include "revision.hh"
28
29using std::deque;
30using std::exception;
31using std::make_pair;
32using std::map;
33using std::pair;
34using std::set;
35using std::string;
36using std::vector;
37
38using boost::lexical_cast;
39
40// workspace / book-keeping file code
41
42static string const attr_file_name(".mt-attrs");
43static string const inodeprints_file_name("inodeprints");
44static string const local_dump_file_name("debug");
45static string const options_file_name("options");
46static string const work_file_name("work");
47static string const user_log_file_name("log");
48
49
50// attribute map file
51
52void
53file_itemizer::visit_dir(file_path const & path)
54{
55 this->visit_file(path);
56}
57
58void
59file_itemizer::visit_file(file_path const & path)
60{
61 split_path sp;
62 path.split(sp);
63
64 if (mask.includes(sp) && known.find(sp) == known.end())
65 {
66 if (app.lua.hook_ignore_file(path) || app.db.is_dbfile(path))
67 ignored.insert(sp);
68 else
69 unknown.insert(sp);
70 }
71}
72
73
74void
75find_missing(roster_t const & new_roster_shape, node_restriction const & mask,
76 path_set & missing)
77{
78 node_map const & nodes = new_roster_shape.all_nodes();
79 for (node_map::const_iterator i = nodes.begin(); i != nodes.end(); ++i)
80 {
81 node_id nid = i->first;
82
83 if (!new_roster_shape.is_root(nid) && mask.includes(new_roster_shape, nid))
84 {
85 split_path sp;
86 new_roster_shape.get_name(nid, sp);
87 file_path fp(sp);
88
89 if (!path_exists(fp))
90 missing.insert(sp);
91 }
92 }
93}
94
95void
96find_unknown_and_ignored(app_state & app, path_restriction const & mask,
97 path_set & unknown, path_set & ignored)
98{
99 revision_t rev;
100 roster_t new_roster;
101 path_set known;
102 temp_node_id_source nis;
103
104 get_current_roster_shape(new_roster, nis, app);
105
106 new_roster.extract_path_set(known);
107
108 file_itemizer u(app, known, unknown, ignored, mask);
109 walk_tree(file_path(), u);
110}
111
112
113class
114addition_builder
115 : public tree_walker
116{
117 app_state & app;
118 roster_t & ros;
119 editable_roster_base & er;
120public:
121 addition_builder(app_state & a,
122 roster_t & r,
123 editable_roster_base & e)
124 : app(a), ros(r), er(e)
125 {}
126 virtual void visit_dir(file_path const & path);
127 virtual void visit_file(file_path const & path);
128 void add_node_for(split_path const & sp);
129};
130
131void
132addition_builder::add_node_for(split_path const & sp)
133{
134 file_path path(sp);
135
136 node_id nid = the_null_node;
137 switch (get_path_status(path))
138 {
139 case path::nonexistent:
140 return;
141 case path::file:
142 {
143 file_id ident;
144 I(ident_existing_file(path, ident, app.lua));
145 nid = er.create_file_node(ident);
146 }
147 break;
148 case path::directory:
149 nid = er.create_dir_node();
150 break;
151 }
152
153 I(nid != the_null_node);
154 er.attach_node(nid, sp);
155
156 map<string, string> attrs;
157 app.lua.hook_init_attributes(path, attrs);
158 if (attrs.size() > 0)
159 {
160 for (map<string, string>::const_iterator i = attrs.begin();
161 i != attrs.end(); ++i)
162 er.set_attr(sp, attr_key(i->first), attr_value(i->second));
163 }
164}
165
166
167void
168addition_builder::visit_dir(file_path const & path)
169{
170 this->visit_file(path);
171}
172
173void
174addition_builder::visit_file(file_path const & path)
175{
176 if (app.lua.hook_ignore_file(path) || app.db.is_dbfile(path))
177 {
178 P(F("skipping ignorable file %s") % path);
179 return;
180 }
181
182 split_path sp;
183 path.split(sp);
184 if (ros.has_node(sp))
185 {
186 if (sp.size() > 1)
187 P(F("skipping %s, already accounted for in workspace") % path);
188 return;
189 }
190
191 P(F("adding %s to workspace manifest") % path);
192
193 split_path dirname, prefix;
194 path_component basename;
195 dirname_basename(sp, dirname, basename);
196 I(ros.has_root());
197 for (split_path::const_iterator i = dirname.begin(); i != dirname.end();
198 ++i)
199 {
200 prefix.push_back(*i);
201 if (i == dirname.begin())
202 continue;
203 if (!ros.has_node(prefix))
204 add_node_for(prefix);
205 }
206
207 add_node_for(sp);
208}
209
210void
211perform_additions(path_set const & paths, app_state & app, bool recursive)
212{
213 if (paths.empty())
214 return;
215
216 temp_node_id_source nis;
217 roster_t base_roster, new_roster;
218 get_base_and_current_roster_shape(base_roster, new_roster, nis, app);
219
220 editable_roster_base er(new_roster, nis);
221
222 if (!new_roster.has_root())
223 {
224 split_path root;
225 root.push_back(the_null_component);
226 er.attach_node(er.create_dir_node(), root);
227 }
228
229 I(new_roster.has_root());
230 addition_builder build(app, new_roster, er);
231
232 for (path_set::const_iterator i = paths.begin(); i != paths.end(); ++i)
233 {
234 if (recursive)
235 {
236 // NB.: walk_tree will handle error checking for non-existent paths
237 walk_tree(file_path(*i), build);
238 }
239 else
240 {
241 // in the case where we're just handled a set of paths, we use the builder
242 // in this strange way.
243 build.visit_file(file_path(*i));
244 }
245 }
246
247 cset new_work;
248 make_cset(base_roster, new_roster, new_work);
249 put_work_cset(new_work);
250 update_any_attrs(app);
251}
252
253void
254perform_deletions(path_set const & paths, app_state & app)
255{
256 if (paths.empty())
257 return;
258
259 temp_node_id_source nis;
260 roster_t base_roster, new_roster;
261 get_base_and_current_roster_shape(base_roster, new_roster, nis, app);
262
263 // we traverse the the paths backwards, so that we always hit deep paths
264 // before shallow paths (because path_set is lexicographically sorted).
265 // this is important in cases like
266 // monotone drop foo/bar foo foo/baz
267 // where, when processing 'foo', we need to know whether or not it is empty
268 // (and thus legal to remove)
269
270 deque<split_path> todo;
271 path_set::const_reverse_iterator i = paths.rbegin();
272 todo.push_back(*i);
273 ++i;
274
275 while (todo.size())
276 {
277 split_path &p(todo.front());
278 file_path name(p);
279
280 if (!new_roster.has_node(p))
281 P(F("skipping %s, not currently tracked") % name);
282 else
283 {
284 node_t n = new_roster.get_node(p);
285 if (is_dir_t(n))
286 {
287 dir_t d = downcast_to_dir_t(n);
288 if (!d->children.empty())
289 {
290 N(app.recursive,
291 F("cannot remove %s/, it is not empty") % name);
292 for (dir_map::const_iterator j = d->children.begin();
293 j != d->children.end(); ++j)
294 {
295 split_path sp = p;
296 sp.push_back(j->first);
297 todo.push_front(sp);
298 }
299 continue;
300 }
301 }
302 P(F("dropping %s from workspace manifest") % name);
303 new_roster.drop_detached_node(new_roster.detach_node(p));
304 if (app.execute && path_exists(name))
305 delete_file_or_dir_shallow(name);
306 }
307 todo.pop_front();
308 if (i != paths.rend())
309 {
310 todo.push_back(*i);
311 ++i;
312 }
313 }
314
315 cset new_work;
316 make_cset(base_roster, new_roster, new_work);
317 put_work_cset(new_work);
318 update_any_attrs(app);
319}
320
321static void
322add_parent_dirs(split_path const & dst, roster_t & ros, node_id_source & nis,
323 app_state & app)
324{
325 editable_roster_base er(ros, nis);
326 addition_builder build(app, ros, er);
327
328 split_path dirname;
329 path_component basename;
330 dirname_basename(dst, dirname, basename);
331
332 // FIXME: this is a somewhat odd way to use the builder
333 build.visit_dir(dirname);
334}
335
336void
337perform_rename(set<file_path> const & src_paths,
338 file_path const & dst_path,
339 app_state & app)
340{
341 temp_node_id_source nis;
342 roster_t base_roster, new_roster;
343 split_path dst;
344 set<split_path> srcs;
345 set< pair<split_path, split_path> > renames;
346
347 I(!src_paths.empty());
348
349 get_base_and_current_roster_shape(base_roster, new_roster, nis, app);
350
351 dst_path.split(dst);
352
353 if (src_paths.size() == 1 && !new_roster.has_node(dst))
354 {
355 // "rename SRC DST" case
356 split_path s;
357 src_paths.begin()->split(s);
358 renames.insert( make_pair(s, dst) );
359 add_parent_dirs(dst, new_roster, nis, app);
360 }
361 else
362 {
363 // "rename SRC1 SRC2 DST" case
364 N(new_roster.has_node(dst),
365 F("destination dir %s/ does not exist in current revision") % dst_path);
366
367 N(is_dir_t(new_roster.get_node(dst)),
368 F("destination %s is an existing file in current revision") % dst_path);
369
370 for (set<file_path>::const_iterator i = src_paths.begin();
371 i != src_paths.end(); i++)
372 {
373 split_path s;
374 i->split(s);
375 // TODO "rename . foo/" might be valid? Or should it already have been
376 // normalised..., in which case it might be an I().
377 N(!s.empty(),
378 F("empty path %s is not allowed") % *i);
379
380 path_component src_basename = s.back();
381 split_path d(dst);
382 d.push_back(src_basename);
383 renames.insert( make_pair(s, d) );
384 }
385 }
386
387 // one iteration to check for existing/missing files
388 for (set< pair<split_path, split_path> >::const_iterator i = renames.begin();
389 i != renames.end(); i++)
390 {
391 N(new_roster.has_node(i->first),
392 F("%s does not exist in current revision") % file_path(i->first));
393
394 N(!new_roster.has_node(i->second),
395 F("destination %s already exists in current revision") % file_path(i->second));
396 }
397
398 // do the attach/detaching
399 for (set< pair<split_path, split_path> >::const_iterator i = renames.begin();
400 i != renames.end(); i++)
401 {
402 node_id nid = new_roster.detach_node(i->first);
403 new_roster.attach_node(nid, i->second);
404 P(F("renaming %s to %s in workspace manifest")
405 % file_path(i->first)
406 % file_path(i->second));
407 }
408
409 cset new_work;
410 make_cset(base_roster, new_roster, new_work);
411 put_work_cset(new_work);
412
413 if (app.execute)
414 {
415 for (set< pair<split_path, split_path> >::const_iterator i = renames.begin();
416 i != renames.end(); i++)
417 {
418 file_path s(i->first);
419 file_path d(i->second);
420 // silently skip files where src doesn't exist or dst does
421 bool have_src = path_exists(s);
422 bool have_dst = path_exists(d);
423 if (have_src && !have_dst)
424 {
425 move_path(s, d);
426 }
427 else if (!have_src && !have_dst)
428 {
429 W(F("%s doesn't exist in workspace, skipping") % s);
430 }
431 else if (have_src && have_dst)
432 {
433 W(F("destination %s already exists in workspace, skipping") % d);
434 }
435 else
436 {
437 L(FL("skipping move_path %s->%s silently, src doesn't exist, dst does")
438 % s % d);
439 }
440 }
441 }
442 update_any_attrs(app);
443}
444
445void
446perform_pivot_root(file_path const & new_root, file_path const & put_old,
447 app_state & app)
448{
449 split_path new_root_sp, put_old_sp, root_sp;
450 new_root.split(new_root_sp);
451 put_old.split(put_old_sp);
452 file_path().split(root_sp);
453
454 temp_node_id_source nis;
455 roster_t base_roster, new_roster;
456 get_base_and_current_roster_shape(base_roster, new_roster, nis, app);
457
458 I(new_roster.has_root());
459 N(new_roster.has_node(new_root_sp),
460 F("proposed new root directory '%s' is not versioned or does not exist") % new_root);
461 N(is_dir_t(new_roster.get_node(new_root_sp)),
462 F("proposed new root directory '%s' is not a directory") % new_root);
463 {
464 split_path new_root__MTN;
465 (new_root / bookkeeping_root.as_internal()).split(new_root__MTN);
466 N(!new_roster.has_node(new_root__MTN),
467 F("proposed new root directory '%s' contains illegal path %s") % new_root % bookkeeping_root);
468 }
469
470 {
471 file_path current_path_to_put_old = (new_root / put_old.as_internal());
472 split_path current_path_to_put_old_sp, current_path_to_put_old_parent_sp;
473 path_component basename;
474 current_path_to_put_old.split(current_path_to_put_old_sp);
475 dirname_basename(current_path_to_put_old_sp, current_path_to_put_old_parent_sp, basename);
476 N(new_roster.has_node(current_path_to_put_old_parent_sp),
477 F("directory '%s' is not versioned or does not exist")
478 % file_path(current_path_to_put_old_parent_sp));
479 N(is_dir_t(new_roster.get_node(current_path_to_put_old_parent_sp)),
480 F("'%s' is not a directory")
481 % file_path(current_path_to_put_old_parent_sp));
482 N(!new_roster.has_node(current_path_to_put_old_sp),
483 F("'%s' is in the way") % current_path_to_put_old);
484 }
485
486 cset cs;
487 safe_insert(cs.nodes_renamed, make_pair(root_sp, put_old_sp));
488 safe_insert(cs.nodes_renamed, make_pair(new_root_sp, root_sp));
489
490 {
491 editable_roster_base e(new_roster, nis);
492 cs.apply_to(e);
493 }
494 {
495 cset new_work;
496 make_cset(base_roster, new_roster, new_work);
497 put_work_cset(new_work);
498 }
499 if (app.execute)
500 {
501 empty_file_content_source efcs;
502 editable_working_tree e(app, efcs);
503 cs.apply_to(e);
504 }
505 update_any_attrs(app);
506}
507
508
509// work file containing rearrangement from uncommitted adds/drops/renames
510
511static void get_work_path(bookkeeping_path & w_path)
512{
513 w_path = bookkeeping_root / work_file_name;
514 L(FL("work path is %s") % w_path);
515}
516
517void get_work_cset(cset & w)
518{
519 bookkeeping_path w_path;
520 get_work_path(w_path);
521 if (path_exists(w_path))
522 {
523 L(FL("checking for un-committed work file %s") % w_path);
524 data w_data;
525 read_data(w_path, w_data);
526 read_cset(w_data, w);
527 L(FL("read cset from %s") % w_path);
528 }
529 else
530 {
531 L(FL("no un-committed work file %s") % w_path);
532 }
533}
534
535void remove_work_cset()
536{
537 bookkeeping_path w_path;
538 get_work_path(w_path);
539 if (file_exists(w_path))
540 delete_file(w_path);
541}
542
543void put_work_cset(cset & w)
544{
545 bookkeeping_path w_path;
546 get_work_path(w_path);
547
548 if (w.empty())
549 {
550 if (file_exists(w_path))
551 delete_file(w_path);
552 }
553 else
554 {
555 data w_data;
556 write_cset(w, w_data);
557 write_data(w_path, w_data);
558 }
559}
560
561// revision file name
562
563string revision_file_name("revision");
564
565static void get_revision_path(bookkeeping_path & m_path)
566{
567 m_path = bookkeeping_root / revision_file_name;
568 L(FL("revision path is %s") % m_path);
569}
570
571void get_revision_id(revision_id & c)
572{
573 c = revision_id();
574 bookkeeping_path c_path;
575 get_revision_path(c_path);
576
577 require_path_is_file(c_path,
578 F("workspace is corrupt: %s does not exist") % c_path,
579 F("workspace is corrupt: %s is a directory") % c_path);
580
581 data c_data;
582 L(FL("loading revision id from %s") % c_path);
583 try
584 {
585 read_data(c_path, c_data);
586 }
587 catch(exception &)
588 {
589 N(false, F("Problem with workspace: %s is unreadable") % c_path);
590 }
591 c = revision_id(remove_ws(c_data()));
592}
593
594void put_revision_id(revision_id const & rev)
595{
596 bookkeeping_path c_path;
597 get_revision_path(c_path);
598 L(FL("writing revision id to %s") % c_path);
599 data c_data(rev.inner()() + "\n");
600 write_data(c_path, c_data);
601}
602
603void
604get_base_revision(app_state & app,
605 revision_id & rid,
606 roster_t & ros,
607 marking_map & mm)
608{
609 get_revision_id(rid);
610
611 if (!null_id(rid))
612 {
613
614 N(app.db.revision_exists(rid),
615 F("base revision %s does not exist in database") % rid);
616
617 app.db.get_roster(rid, ros, mm);
618 }
619
620 L(FL("base roster has %d entries") % ros.all_nodes().size());
621}
622
623void
624get_base_revision(app_state & app,
625 revision_id & rid,
626 roster_t & ros)
627{
628 marking_map mm;
629 get_base_revision(app, rid, ros, mm);
630}
631
632void
633get_base_roster(app_state & app,
634 roster_t & ros)
635{
636 revision_id rid;
637 marking_map mm;
638 get_base_revision(app, rid, ros, mm);
639}
640
641void
642get_current_roster_shape(roster_t & ros, node_id_source & nis, app_state & app)
643{
644 get_base_roster(app, ros);
645 cset cs;
646 get_work_cset(cs);
647 editable_roster_base er(ros, nis);
648 cs.apply_to(er);
649}
650
651void
652get_base_and_current_roster_shape(roster_t & base_roster,
653 roster_t & current_roster,
654 node_id_source & nis,
655 app_state & app)
656{
657 get_base_roster(app, base_roster);
658 current_roster = base_roster;
659 cset cs;
660 get_work_cset(cs);
661 editable_roster_base er(current_roster, nis);
662 cs.apply_to(er);
663}
664
665// user log file
666
667void
668get_user_log_path(bookkeeping_path & ul_path)
669{
670 ul_path = bookkeeping_root / user_log_file_name;
671 L(FL("user log path is %s") % ul_path);
672}
673
674void
675read_user_log(data & dat)
676{
677 bookkeeping_path ul_path;
678 get_user_log_path(ul_path);
679
680 if (file_exists(ul_path))
681 {
682 read_data(ul_path, dat);
683 }
684}
685
686void
687write_user_log(data const & dat)
688{
689 bookkeeping_path ul_path;
690 get_user_log_path(ul_path);
691
692 write_data(ul_path, dat);
693}
694
695void
696blank_user_log()
697{
698 data empty;
699 bookkeeping_path ul_path;
700 get_user_log_path(ul_path);
701 write_data(ul_path, empty);
702}
703
704bool
705has_contents_user_log()
706{
707 data user_log_message;
708 read_user_log(user_log_message);
709 return user_log_message().length() > 0;
710}
711
712// options map file
713
714void
715get_options_path(bookkeeping_path & o_path)
716{
717 o_path = bookkeeping_root / options_file_name;
718 L(FL("options path is %s") % o_path);
719}
720
721void
722read_options_map(data const & dat, options_map & options)
723{
724 basic_io::input_source src(dat(), "_MTN/options");
725 basic_io::tokenizer tok(src);
726 basic_io::parser parser(tok);
727
728 // don't clear the options which will have settings from the command line
729 // options.clear();
730
731 string opt, val;
732 while (parser.symp())
733 {
734 parser.sym(opt);
735 parser.str(val);
736 // options[opt] = val;
737 // use non-replacing insert versus replacing with options[opt] = val;
738 options.insert(make_pair(opt, val));
739 }
740}
741
742void
743write_options_map(data & dat, options_map const & options)
744{
745 basic_io::printer pr;
746
747 basic_io::stanza st;
748 for (options_map::const_iterator i = options.begin();
749 i != options.end(); ++i)
750 st.push_str_pair(i->first, i->second());
751
752 pr.print_stanza(st);
753 dat = pr.buf;
754}
755
756// local dump file
757
758void get_local_dump_path(bookkeeping_path & d_path)
759{
760 d_path = bookkeeping_root / local_dump_file_name;
761 L(FL("local dump path is %s") % d_path);
762}
763
764// inodeprint file
765
766void
767get_inodeprints_path(bookkeeping_path & ip_path)
768{
769 ip_path = bookkeeping_root / inodeprints_file_name;
770}
771
772bool
773in_inodeprints_mode()
774{
775 bookkeeping_path ip_path;
776 get_inodeprints_path(ip_path);
777 return file_exists(ip_path);
778}
779
780void
781read_inodeprints(data & dat)
782{
783 I(in_inodeprints_mode());
784 bookkeeping_path ip_path;
785 get_inodeprints_path(ip_path);
786 read_data(ip_path, dat);
787}
788
789void
790write_inodeprints(data const & dat)
791{
792 I(in_inodeprints_mode());
793 bookkeeping_path ip_path;
794 get_inodeprints_path(ip_path);
795 write_data(ip_path, dat);
796}
797
798void
799enable_inodeprints()
800{
801 bookkeeping_path ip_path;
802 get_inodeprints_path(ip_path);
803 data dat;
804 write_data(ip_path, dat);
805}
806
807
808bool
809get_attribute_from_roster(roster_t const & ros,
810 file_path const & path,
811 attr_key const & key,
812 attr_value & val)
813{
814 split_path sp;
815 path.split(sp);
816 if (ros.has_node(sp))
817 {
818 node_t n = ros.get_node(sp);
819 full_attr_map_t::const_iterator i = n->attrs.find(key);
820 if (i != n->attrs.end() && i->second.first)
821 {
822 val = i->second.second;
823 return true;
824 }
825 }
826 return false;
827}
828
829
830void update_any_attrs(app_state & app)
831{
832 temp_node_id_source nis;
833 roster_t new_roster;
834 get_current_roster_shape(new_roster, nis, app);
835 node_map const & nodes = new_roster.all_nodes();
836 for (node_map::const_iterator i = nodes.begin();
837 i != nodes.end(); ++i)
838 {
839 split_path sp;
840 new_roster.get_name(i->first, sp);
841
842 // FIXME_RESTRICTIONS: do we need this check?
843 // if (!app.restriction_includes(sp))
844 // continue;
845
846 node_t n = i->second;
847 for (full_attr_map_t::const_iterator j = n->attrs.begin();
848 j != n->attrs.end(); ++j)
849 {
850 if (j->second.first)
851 {
852 app.lua.hook_apply_attribute (j->first(),
853 file_path(sp),
854 j->second.second());
855 }
856 }
857 }
858}
859
860editable_working_tree::editable_working_tree(app_state & app,
861 file_content_source const & source)
862 : app(app), source(source), next_nid(1), root_dir_attached(true)
863{
864}
865
866static inline bookkeeping_path
867path_for_nid(node_id nid)
868{
869 return bookkeeping_root / "tmp" / lexical_cast<string>(nid);
870}
871
872// Attaching/detaching the root directory:
873// This is tricky, because we don't want to simply move it around, like
874// other directories. That would require some very snazzy handling of the
875// _MTN directory, and never be possible on windows anyway[1]. So, what we do
876// is fake it -- whenever we want to move the root directory into the
877// temporary dir, we instead create a new dir in the temporary dir, move
878// all of the root's contents into this new dir, and make a note that the root
879// directory is logically non-existent. Whenever we want to move some
880// directory out of the temporary dir and onto the root directory, we instead
881// check that the root is logically nonexistent, move its contents, and note
882// that it exists again.
883//
884// [1] Because the root directory is our working directory, and thus locked in
885// place. We _could_ chdir out, then move _MTN out, then move the real root
886// directory into our newly-moved _MTN, etc., but aside from being very finicky,
887// this would require that we know our root directory's name relative to its
888// parent.
889
890node_id
891editable_working_tree::detach_node(split_path const & src)
892{
893 I(root_dir_attached);
894 node_id nid = next_nid++;
895 file_path src_pth(src);
896 bookkeeping_path dst_pth = path_for_nid(nid);
897 safe_insert(rename_add_drop_map, make_pair(dst_pth, src_pth));
898 make_dir_for(dst_pth);
899 if (src_pth == file_path())
900 {
901 // root dir detach, so we move contents, rather than the dir itself
902 mkdir_p(dst_pth);
903 vector<utf8> files, dirs;
904 read_directory(src_pth, files, dirs);
905 for (vector<utf8>::const_iterator i = files.begin(); i != files.end(); ++i)
906 move_file(src_pth / (*i)(), dst_pth / (*i)());
907 for (vector<utf8>::const_iterator i = dirs.begin(); i != dirs.end(); ++i)
908 if (!bookkeeping_path::is_bookkeeping_path((*i)()))
909 move_dir(src_pth / (*i)(), dst_pth / (*i)());
910 root_dir_attached = false;
911 }
912 else
913 move_path(src_pth, dst_pth);
914 return nid;
915}
916
917void
918editable_working_tree::drop_detached_node(node_id nid)
919{
920 bookkeeping_path pth = path_for_nid(nid);
921 map<bookkeeping_path, file_path>::const_iterator i
922 = rename_add_drop_map.find(pth);
923 I(i != rename_add_drop_map.end());
924 P(F("dropping %s") % i->second);
925 safe_erase(rename_add_drop_map, pth);
926 delete_file_or_dir_shallow(pth);
927}
928
929node_id
930editable_working_tree::create_dir_node()
931{
932 node_id nid = next_nid++;
933 bookkeeping_path pth = path_for_nid(nid);
934 require_path_is_nonexistent(pth,
935 F("path %s already exists") % pth);
936 mkdir_p(pth);
937 return nid;
938}
939
940node_id
941editable_working_tree::create_file_node(file_id const & content)
942{
943 node_id nid = next_nid++;
944 bookkeeping_path pth = path_for_nid(nid);
945 require_path_is_nonexistent(pth,
946 F("path %s already exists") % pth);
947 safe_insert(written_content, make_pair(pth, content));
948 // Defer actual write to moment of attachment, when we know the path
949 // and can thus determine encoding / linesep convention.
950 return nid;
951}
952
953void
954editable_working_tree::attach_node(node_id nid, split_path const & dst)
955{
956 bookkeeping_path src_pth = path_for_nid(nid);
957 file_path dst_pth(dst);
958
959 // Possibly just write data out into the workspace, if we're doing
960 // a file-create (not a dir-create or file/dir rename).
961 if (!path_exists(src_pth))
962 {
963 I(root_dir_attached);
964 map<bookkeeping_path, file_id>::const_iterator i
965 = written_content.find(src_pth);
966 if (i != written_content.end())
967 {
968 P(F("adding %s") % dst_pth);
969 file_data dat;
970 source.get_file_content(i->second, dat);
971 write_localized_data(dst_pth, dat.inner(), app.lua);
972 return;
973 }
974 }
975
976 // FIXME: it is weird to do this here, instead of up above, but if we do it
977 // up above a lot of tests break. those tests are arguably broken -- they
978 // depend on 'update' clobbering existing, non-versioned files -- but
979 // putting this up there doesn't actually help, since if we abort in the
980 // middle of an update to avoid clobbering a file, we just end up leaving
981 // the working copy in an inconsistent state instead. so for now, we leave
982 // this check down here.
983 require_path_is_nonexistent(dst_pth,
984 F("path '%s' already exists, cannot create") % dst_pth);
985
986 // If we get here, we're doing a file/dir rename, or a dir-create.
987 map<bookkeeping_path, file_path>::const_iterator i
988 = rename_add_drop_map.find(src_pth);
989 if (i != rename_add_drop_map.end())
990 {
991 P(F("renaming %s to %s") % i->second % dst_pth);
992 safe_erase(rename_add_drop_map, src_pth);
993 }
994 else
995 P(F("adding %s") % dst_pth);
996 if (dst_pth == file_path())
997 {
998 // root dir attach, so we move contents, rather than the dir itself
999 vector<utf8> files, dirs;
1000 read_directory(src_pth, files, dirs);
1001 for (vector<utf8>::const_iterator i = files.begin(); i != files.end(); ++i)
1002 {
1003 I(!bookkeeping_path::is_bookkeeping_path((*i)()));
1004 move_file(src_pth / (*i)(), dst_pth / (*i)());
1005 }
1006 for (vector<utf8>::const_iterator i = dirs.begin(); i != dirs.end(); ++i)
1007 {
1008 I(!bookkeeping_path::is_bookkeeping_path((*i)()));
1009 move_dir(src_pth / (*i)(), dst_pth / (*i)());
1010 }
1011 delete_dir_shallow(src_pth);
1012 root_dir_attached = true;
1013 }
1014 else
1015 // This will complain if the move is actually impossible
1016 move_path(src_pth, dst_pth);
1017}
1018
1019void
1020editable_working_tree::apply_delta(split_path const & pth,
1021 file_id const & old_id,
1022 file_id const & new_id)
1023{
1024 file_path pth_unsplit(pth);
1025 require_path_is_file(pth_unsplit,
1026 F("file '%s' does not exist") % pth_unsplit,
1027 F("file '%s' is a directory") % pth_unsplit);
1028 hexenc<id> curr_id_raw;
1029 calculate_ident(pth_unsplit, curr_id_raw, app.lua);
1030 file_id curr_id(curr_id_raw);
1031 E(curr_id == old_id,
1032 F("content of file '%s' has changed, not overwriting") % pth_unsplit);
1033 P(F("modifying %s") % pth_unsplit);
1034
1035 file_data dat;
1036 source.get_file_content(new_id, dat);
1037 write_localized_data(pth_unsplit, dat.inner(), app.lua);
1038}
1039
1040void
1041editable_working_tree::clear_attr(split_path const & pth,
1042 attr_key const & name)
1043{
1044 // FIXME_ROSTERS: call a lua hook
1045}
1046
1047void
1048editable_working_tree::set_attr(split_path const & pth,
1049 attr_key const & name,
1050 attr_value const & val)
1051{
1052 // FIXME_ROSTERS: call a lua hook
1053}
1054
1055void
1056editable_working_tree::commit()
1057{
1058 I(rename_add_drop_map.empty());
1059 I(root_dir_attached);
1060}
1061
1062editable_working_tree::~editable_working_tree()
1063{
1064}
1065
1066// Local Variables:
1067// mode: C++
1068// fill-column: 76
1069// c-file-style: "gnu"
1070// indent-tabs-mode: nil
1071// End:
1072// 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