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 | ␊ |
34 | using std::deque;␊ |
35 | using std::exception;␊ |
36 | using std::make_pair;␊ |
37 | using std::map;␊ |
38 | using std::pair;␊ |
39 | using std::set;␊ |
40 | using std::string;␊ |
41 | using std::vector;␊ |
42 | ␊ |
43 | using boost::lexical_cast;␊ |
44 | ␊ |
45 | // workspace / book-keeping file code␊ |
46 | ␊ |
47 | static char const inodeprints_file_name[] = "inodeprints";␊ |
48 | static char const local_dump_file_name[] = "debug";␊ |
49 | static char const options_file_name[] = "options";␊ |
50 | static char const user_log_file_name[] = "log";␊ |
51 | static char const revision_file_name[] = "revision";␊ |
52 | ␊ |
53 | static void␊ |
54 | get_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 | ␊ |
60 | static void␊ |
61 | get_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 | ␊ |
67 | static void␊ |
68 | get_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 | ␊ |
74 | static void␊ |
75 | get_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␊ |
84 | void␊ |
85 | workspace::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 | ␊ |
106 | void␊ |
107 | workspace::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 | ␊ |
124 | static void␊ |
125 | get_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 | ␊ |
146 | void␊ |
147 | workspace::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 | ␊ |
161 | void␊ |
162 | workspace::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 | ␊ |
185 | void␊ |
186 | workspace::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 | ␊ |
192 | void␊ |
193 | workspace::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 | ␊ |
206 | void␊ |
207 | workspace::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 | ␊ |
217 | void␊ |
218 | workspace::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 | ␊ |
226 | bool␊ |
227 | workspace::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 | ␊ |
236 | void␊ |
237 | workspace::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 | ␊ |
247 | bool␊ |
248 | workspace::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 | ␊ |
311 | void␊ |
312 | workspace::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 | ␊ |
367 | void␊ |
368 | workspace::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 | ␊ |
376 | static bool␊ |
377 | in_inodeprints_mode()␊ |
378 | {␊ |
379 | bookkeeping_path ip_path;␊ |
380 | get_inodeprints_path(ip_path);␊ |
381 | return file_exists(ip_path);␊ |
382 | }␊ |
383 | ␊ |
384 | static void␊ |
385 | read_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 | ␊ |
393 | static void␊ |
394 | write_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 | ␊ |
402 | void␊ |
403 | workspace::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 | ␊ |
411 | void␊ |
412 | workspace::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␊ |
469 | namespace {␊ |
470 | ␊ |
471 | struct 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 | ␊ |
490 | bool␊ |
491 | file_itemizer::visit_dir(file_path const & path)␊ |
492 | {␊ |
493 | this->visit_file(path);␊ |
494 | return known.find(path) != known.end();␊ |
495 | }␊ |
496 | ␊ |
497 | void␊ |
498 | file_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 | ␊ |
510 | struct 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 | ␊ |
522 | workspace_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 | ␊ |
531 | bool␊ |
532 | workspace_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 | ␊ |
539 | void␊ |
540 | workspace_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 | ␊ |
548 | class␊ |
549 | addition_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;␊ |
557 | public:␊ |
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 | ␊ |
568 | void␊ |
569 | addition_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 | ␊ |
614 | bool␊ |
615 | addition_builder::visit_dir(file_path const & path)␊ |
616 | {␊ |
617 | this->visit_file(path);␊ |
618 | return true;␊ |
619 | }␊ |
620 | ␊ |
621 | void␊ |
622 | addition_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 | ␊ |
641 | struct 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();␊ |
668 | private:␊ |
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 | ␊ |
678 | struct 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 | ␊ |
712 | struct 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 | ␊ |
727 | static inline bookkeeping_path␊ |
728 | path_for_detached_nids()␊ |
729 | {␊ |
730 | return bookkeeping_root / "detached";␊ |
731 | }␊ |
732 | ␊ |
733 | static inline bookkeeping_path␊ |
734 | path_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 | ␊ |
757 | node_id␊ |
758 | editable_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 | ␊ |
784 | void␊ |
785 | editable_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 | ␊ |
796 | node_id␊ |
797 | editable_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 | ␊ |
807 | node_id␊ |
808 | editable_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 | ␊ |
821 | void␊ |
822 | editable_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 | ␊ |
862 | void␊ |
863 | editable_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 | ␊ |
882 | void␊ |
883 | editable_working_tree::clear_attr(file_path const & pth,␊ |
884 | attr_key const & name)␊ |
885 | {␊ |
886 | // FIXME_ROSTERS: call a lua hook␊ |
887 | }␊ |
888 | ␊ |
889 | void␊ |
890 | editable_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 | ␊ |
897 | void␊ |
898 | editable_working_tree::commit()␊ |
899 | {␊ |
900 | I(rename_add_drop_map.empty());␊ |
901 | I(root_dir_attached);␊ |
902 | }␊ |
903 | ␊ |
904 | editable_working_tree::~editable_working_tree()␊ |
905 | {␊ |
906 | }␊ |
907 | ␊ |
908 | ␊ |
909 | node_id␊ |
910 | simulated_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 | ␊ |
917 | void␊ |
918 | simulated_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 | ␊ |
934 | node_id␊ |
935 | simulated_working_tree::create_dir_node()␊ |
936 | {␊ |
937 | return workspace.create_dir_node(nis);␊ |
938 | }␊ |
939 | ␊ |
940 | node_id␊ |
941 | simulated_working_tree::create_file_node(file_id const & content)␊ |
942 | {␊ |
943 | return workspace.create_file_node(content, nis);␊ |
944 | }␊ |
945 | ␊ |
946 | void␊ |
947 | simulated_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 | ␊ |
983 | void␊ |
984 | simulated_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 | ␊ |
992 | void␊ |
993 | simulated_working_tree::clear_attr(file_path const & pth,␊ |
994 | attr_key const & name)␊ |
995 | {␊ |
996 | }␊ |
997 | ␊ |
998 | void␊ |
999 | simulated_working_tree::set_attr(file_path const & pth,␊ |
1000 | attr_key const & name,␊ |
1001 | attr_value const & val)␊ |
1002 | {␊ |
1003 | }␊ |
1004 | ␊ |
1005 | void␊ |
1006 | simulated_working_tree::commit()␊ |
1007 | {␊ |
1008 | N(conflicts == 0, F("%d workspace conflicts") % conflicts);␊ |
1009 | }␊ |
1010 | ␊ |
1011 | simulated_working_tree::~simulated_working_tree()␊ |
1012 | {␊ |
1013 | }␊ |
1014 | ␊ |
1015 | ␊ |
1016 | }; // anonymous namespace␊ |
1017 | ␊ |
1018 | static void␊ |
1019 | add_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 | ␊ |
1029 | inline static bool␊ |
1030 | inodeprint_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 | ␊ |
1053 | void␊ |
1054 | workspace::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 | ␊ |
1120 | void␊ |
1121 | workspace::update_current_roster_from_filesystem(roster_t & ros)␊ |
1122 | {␊ |
1123 | update_current_roster_from_filesystem(ros, node_restriction());␊ |
1124 | }␊ |
1125 | ␊ |
1126 | void␊ |
1127 | workspace::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 | ␊ |
1212 | void␊ |
1213 | workspace::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 | ␊ |
1233 | void␊ |
1234 | workspace::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 | ␊ |
1254 | void␊ |
1255 | workspace::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 | ␊ |
1311 | static bool␊ |
1312 | in_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 | ␊ |
1325 | void␊ |
1326 | workspace::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 | ␊ |
1417 | void␊ |
1418 | workspace::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 | ␊ |
1528 | void␊ |
1529 | workspace::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 | ␊ |
1590 | void␊ |
1591 | workspace::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 | ␊ |
1623 | void␊ |
1624 | workspace::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:␊ |