monotone

monotone Mtn Source Tree

Root/cmd_ws_commit.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 <iostream>
11#include <map>
12
13#include "cmd.hh"
14#include "diff_patch.hh"
15#include "localized_file_io.hh"
16#include "packet.hh"
17#include "restrictions.hh"
18#include "revision.hh"
19#include "transforms.hh"
20#include "work.hh"
21
22using std::cout;
23using std::make_pair;
24using std::pair;
25using std::map;
26using std::set;
27using std::string;
28using std::vector;
29
30using boost::shared_ptr;
31
32static void
33get_log_message_interactively(revision_t const & cs,
34 app_state & app,
35 string & log_message)
36{
37 string commentary;
38 data summary, user_log_message;
39 write_revision(cs, summary);
40 read_user_log(user_log_message);
41 commentary += string(70, '-') + "\n";
42 commentary += _("Enter a description of this change.\n"
43 "Lines beginning with `MTN:' "
44 "are removed automatically.\n");
45 commentary += "\n";
46 commentary += summary();
47 commentary += string(70, '-') + "\n";
48
49 N(app.lua.hook_edit_comment(commentary, user_log_message(), log_message),
50 F("edit of log message failed"));
51}
52
53CMD(revert, N_("workspace"), N_("[PATH]..."),
54 N_("revert file(s), dir(s) or entire workspace (\".\")"),
55 OPT_DEPTH % OPT_EXCLUDE % OPT_MISSING)
56{
57 temp_node_id_source nis;
58 roster_t old_roster, new_roster;
59 cset included, excluded;
60
61 N(app.missing || !args.empty() || !app.exclude_patterns.empty(),
62 F("you must pass at least one path to 'revert' (perhaps '.')"));
63
64 app.require_workspace();
65
66 get_base_and_current_roster_shape(old_roster, new_roster, nis, app);
67
68 node_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns),
69 old_roster, new_roster, app);
70
71 if (app.missing)
72 {
73 // --missing is a further filter on the files included by a
74 // restriction we first find all missing files included by the
75 // specified args and then make a restriction that includes only
76 // these missing files.
77 path_set missing;
78 find_missing(new_roster, mask, missing);
79 if (missing.empty())
80 {
81 P(F("no missing files to revert"));
82 return;
83 }
84
85 std::vector<file_path> missing_files;
86 for (path_set::const_iterator i = missing.begin(); i != missing.end(); i++)
87 {
88 file_path fp(*i);
89 L(FL("missing files are '%s'") % fp);
90 missing_files.push_back(fp);
91 }
92 // replace the original mask with a more restricted one
93 mask = node_restriction(missing_files, std::vector<file_path>(),
94 old_roster, new_roster, app);
95 }
96
97 make_restricted_csets(old_roster, new_roster,
98 included, excluded, mask);
99
100 // The included cset will be thrown away (reverted) leaving the
101 // excluded cset pending in MTN/work which must be valid against the
102 // old roster.
103
104 check_restricted_cset(old_roster, excluded);
105
106 node_map const & nodes = old_roster.all_nodes();
107 for (node_map::const_iterator i = nodes.begin();
108 i != nodes.end(); ++i)
109 {
110 node_id nid = i->first;
111 node_t node = i->second;
112
113 if (old_roster.is_root(nid))
114 continue;
115
116 split_path sp;
117 old_roster.get_name(nid, sp);
118 file_path fp(sp);
119
120 if (!mask.includes(old_roster, nid))
121 continue;
122
123 if (is_file_t(node))
124 {
125 file_t f = downcast_to_file_t(node);
126 if (file_exists(fp))
127 {
128 hexenc<id> ident;
129 calculate_ident(fp, ident, app.lua);
130 // don't touch unchanged files
131 if (ident == f->content.inner())
132 continue;
133 }
134
135 P(F("reverting %s") % fp);
136 L(FL("reverting %s to [%s]") % fp % f->content);
137
138 N(app.db.file_version_exists(f->content),
139 F("no file version %s found in database for %s")
140 % f->content % fp);
141
142 file_data dat;
143 L(FL("writing file %s to %s")
144 % f->content % fp);
145 app.db.get_file_version(f->content, dat);
146 write_localized_data(fp, dat.inner(), app.lua);
147 }
148 else
149 {
150 if (!directory_exists(fp))
151 {
152 P(F("recreating %s/") % fp);
153 mkdir_p(fp);
154 }
155 }
156 }
157
158 // Included_work is thrown away which effectively reverts any adds,
159 // drops and renames it contains. Drops and rename sources will have
160 // been rewritten above but this may leave rename targets laying
161 // around.
162
163 // Race.
164 put_work_cset(excluded);
165 update_any_attrs(app);
166 maybe_update_inodeprints(app);
167}
168
169CMD(disapprove, N_("review"), N_("REVISION"),
170 N_("disapprove of a particular revision"),
171 OPT_BRANCH_NAME)
172{
173 if (args.size() != 1)
174 throw usage(name);
175
176 revision_id r;
177 revision_t rev, rev_inverse;
178 shared_ptr<cset> cs_inverse(new cset());
179 complete(app, idx(args, 0)(), r);
180 app.db.get_revision(r, rev);
181
182 N(rev.edges.size() == 1,
183 F("revision '%s' has %d changesets, cannot invert\n") % r % rev.edges.size());
184
185 cert_value branchname;
186 guess_branch(r, app, branchname);
187 N(app.branch_name() != "", F("need --branch argument for disapproval"));
188
189 edge_entry const & old_edge (*rev.edges.begin());
190 app.db.get_revision_manifest(edge_old_revision(old_edge),
191 rev_inverse.new_manifest);
192 {
193 roster_t old_roster, new_roster;
194 app.db.get_roster(edge_old_revision(old_edge), old_roster);
195 app.db.get_roster(r, new_roster);
196 make_cset(new_roster, old_roster, *cs_inverse);
197 }
198 rev_inverse.edges.insert(make_pair(r, cs_inverse));
199
200 {
201 transaction_guard guard(app.db);
202 packet_db_writer dbw(app);
203
204 revision_id inv_id;
205 revision_data rdat;
206
207 write_revision(rev_inverse, rdat);
208 calculate_ident(rdat, inv_id);
209 dbw.consume_revision_data(inv_id, rdat);
210
211 cert_revision_in_branch(inv_id, branchname, app, dbw);
212 cert_revision_date_now(inv_id, app, dbw);
213 cert_revision_author_default(inv_id, app, dbw);
214 cert_revision_changelog(inv_id,
215 (FL("disapproval of revision '%s'")
216 % r).str(), app, dbw);
217 guard.commit();
218 }
219}
220
221
222CMD(add, N_("workspace"), N_("[PATH]..."),
223 N_("add files to workspace"), OPT_UNKNOWN)
224{
225 if (!app.unknown && (args.size() < 1))
226 throw usage(name);
227
228 app.require_workspace();
229
230 path_set paths;
231 if (app.unknown)
232 {
233 path_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns), app);
234 path_set ignored;
235 find_unknown_and_ignored(app, mask, paths, ignored);
236 }
237 else
238 for (vector<utf8>::const_iterator i = args.begin();
239 i != args.end(); ++i)
240 {
241 split_path sp;
242 file_path_external(*i).split(sp);
243 paths.insert(sp);
244 }
245
246 bool add_recursive = !app.unknown;
247 perform_additions(paths, app, add_recursive);
248}
249
250CMD(drop, N_("workspace"), N_("[PATH]..."),
251 N_("drop files from workspace"),
252 OPT_EXECUTE % OPT_MISSING % OPT_RECURSIVE)
253{
254 if (!app.missing && (args.size() < 1))
255 throw usage(name);
256
257 app.require_workspace();
258
259 path_set paths;
260 if (app.missing)
261 {
262 temp_node_id_source nis;
263 roster_t current_roster_shape;
264 get_current_roster_shape(current_roster_shape, nis, app);
265 node_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns),
266 current_roster_shape, app);
267 find_missing(current_roster_shape, mask, paths);
268 }
269 else
270 for (vector<utf8>::const_iterator i = args.begin();
271 i != args.end(); ++i)
272 {
273 split_path sp;
274 file_path_external(*i).split(sp);
275 paths.insert(sp);
276 }
277
278 perform_deletions(paths, app);
279}
280
281ALIAS(rm, drop);
282
283
284CMD(rename, N_("workspace"),
285 N_("SRC DEST\n"
286 "SRC1 [SRC2 [...]] DEST_DIR"),
287 N_("rename entries in the workspace"),
288 OPT_EXECUTE)
289{
290 if (args.size() < 2)
291 throw usage(name);
292
293 app.require_workspace();
294
295 file_path dst_path = file_path_external(args.back());
296
297 set<file_path> src_paths;
298 for (size_t i = 0; i < args.size()-1; i++)
299 {
300 file_path s = file_path_external(idx(args, i));
301 src_paths.insert(s);
302 }
303 perform_rename(src_paths, dst_path, app);
304}
305
306ALIAS(mv, rename)
307
308
309 CMD(pivot_root, N_("workspace"), N_("NEW_ROOT PUT_OLD"),
310 N_("rename the root directory\n"
311 "after this command, the directory that currently "
312 "has the name NEW_ROOT\n"
313 "will be the root directory, and the directory "
314 "that is currently the root\n"
315 "directory will have name PUT_OLD.\n"
316 "Using --execute is strongly recommended."),
317 OPT_EXECUTE)
318{
319 if (args.size() != 2)
320 throw usage(name);
321
322 app.require_workspace();
323 file_path new_root = file_path_external(idx(args, 0));
324 file_path put_old = file_path_external(idx(args, 1));
325 perform_pivot_root(new_root, put_old, app);
326}
327
328CMD(status, N_("informative"), N_("[PATH]..."),
329 N_("show status of workspace"),
330 OPT_DEPTH % OPT_EXCLUDE % OPT_BRIEF)
331{
332 roster_t old_roster, new_roster, restricted_roster;
333 cset included, excluded;
334 revision_id old_rev_id;
335 revision_t rev;
336 data tmp;
337 temp_node_id_source nis;
338
339 app.require_workspace();
340 get_base_and_current_roster_shape(old_roster, new_roster, nis, app);
341
342 node_restriction mask(args_to_paths(args),
343 args_to_paths(app.exclude_patterns),
344 old_roster, new_roster, app);
345
346 update_current_roster_from_filesystem(new_roster, mask, app);
347 make_restricted_csets(old_roster, new_roster,
348 included, excluded, mask);
349 check_restricted_cset(old_roster, included);
350
351 restricted_roster = old_roster;
352 editable_roster_base er(restricted_roster, nis);
353 included.apply_to(er);
354
355 get_revision_id(old_rev_id);
356 make_revision(old_rev_id, old_roster, restricted_roster, rev);
357
358 if (global_sanity.brief)
359 {
360 I(rev.edges.size() == 1);
361 cset const & cs = edge_changes(rev.edges.begin());
362
363 for (path_set::const_iterator i = cs.nodes_deleted.begin();
364 i != cs.nodes_deleted.end(); ++i)
365 cout << "dropped " << *i << "\n";
366
367 for (map<split_path, split_path>::const_iterator
368 i = cs.nodes_renamed.begin();
369 i != cs.nodes_renamed.end(); ++i)
370 cout << "renamed " << i->first << "\n"
371 << " to " << i->second << "\n";
372
373 for (path_set::const_iterator i = cs.dirs_added.begin();
374 i != cs.dirs_added.end(); ++i)
375 cout << "added " << *i << "\n";
376
377 for (map<split_path, file_id>::const_iterator i = cs.files_added.begin();
378 i != cs.files_added.end(); ++i)
379 cout << "added " << i->first << "\n";
380
381 for (map<split_path, pair<file_id, file_id> >::const_iterator
382 i = cs.deltas_applied.begin(); i != cs.deltas_applied.end(); ++i)
383 cout << "patched " << i->first << "\n";
384 }
385 else
386 {
387 write_revision(rev, tmp);
388 cout << "\n" << tmp << "\n";
389 }
390}
391
392CMD(checkout, N_("tree"), N_("[DIRECTORY]\n"),
393 N_("check out a revision from database into directory.\n"
394 "If a revision is given, that's the one that will be checked out.\n"
395 "Otherwise, it will be the head of the branch (given or implicit).\n"
396 "If no directory is given, the branch name will be used as directory"),
397 OPT_BRANCH_NAME % OPT_REVISION)
398{
399 revision_id ident;
400 system_path dir;
401 // We have a special case for "checkout .", i.e., to current dir.
402 bool checkout_dot = false;
403
404 transaction_guard guard(app.db, false);
405
406 if (args.size() > 1 || app.revision_selectors.size() > 1)
407 throw usage(name);
408
409 if (args.size() == 0)
410 {
411 // No checkout dir specified, use branch name for dir.
412 N(!app.branch_name().empty(),
413 F("need --branch argument for branch-based checkout"));
414 dir = system_path(app.branch_name());
415 }
416 else
417 {
418 // Checkout to specified dir.
419 dir = system_path(idx(args, 0));
420 if (idx(args, 0) == utf8("."))
421 checkout_dot = true;
422 }
423
424 if (!checkout_dot)
425 require_path_is_nonexistent
426 (dir, F("checkout directory '%s' already exists") % dir);
427
428 if (app.revision_selectors.size() == 0)
429 {
430 // use branch head revision
431 N(!app.branch_name().empty(),
432 F("need --branch argument for branch-based checkout"));
433
434 set<revision_id> heads;
435 get_branch_heads(app.branch_name(), app, heads);
436 N(heads.size() > 0,
437 F("branch '%s' is empty") % app.branch_name);
438 if (heads.size() > 1)
439 {
440 P(F("branch %s has multiple heads:") % app.branch_name);
441 for (set<revision_id>::const_iterator i = heads.begin(); i != heads.end(); ++i)
442 P(i18n_format(" %s") % describe_revision(app, *i));
443 P(F("choose one with '%s checkout -r<id>'") % app.prog_name);
444 E(false, F("branch %s has multiple heads") % app.branch_name);
445 }
446 ident = *(heads.begin());
447 }
448 else if (app.revision_selectors.size() == 1)
449 {
450 // use specified revision
451 complete(app, idx(app.revision_selectors, 0)(), ident);
452 N(app.db.revision_exists(ident),
453 F("no such revision '%s'") % ident);
454
455 cert_value b;
456 guess_branch(ident, app, b);
457
458 I(!app.branch_name().empty());
459 cert_value branch_name(app.branch_name());
460 base64<cert_value> branch_encoded;
461 encode_base64(branch_name, branch_encoded);
462
463 vector< revision<cert> > certs;
464 app.db.get_revision_certs(ident, branch_cert_name, branch_encoded, certs);
465
466 L(FL("found %d %s branch certs on revision %s")
467 % certs.size()
468 % app.branch_name
469 % ident);
470
471 N(certs.size() != 0, F("revision %s is not a member of branch %s")
472 % ident % app.branch_name);
473 }
474
475 app.create_workspace(dir);
476
477 file_data data;
478 roster_t ros;
479 marking_map mm;
480
481 put_revision_id(ident);
482
483 L(FL("checking out revision %s to directory %s") % ident % dir);
484 app.db.get_roster(ident, ros, mm);
485
486 node_map const & nodes = ros.all_nodes();
487 for (node_map::const_iterator i = nodes.begin();
488 i != nodes.end(); ++i)
489 {
490 node_t node = i->second;
491 split_path sp;
492 ros.get_name(i->first, sp);
493 file_path path(sp);
494
495 if (is_dir_t(node))
496 {
497 if (!workspace_root(sp))
498 mkdir_p(path);
499 }
500 else
501 {
502 file_t file = downcast_to_file_t(node);
503 N(app.db.file_version_exists(file->content),
504 F("no file %s found in database for %s")
505 % file->content % path);
506
507 file_data dat;
508 L(FL("writing file %s to %s")
509 % file->content % path);
510 app.db.get_file_version(file->content, dat);
511 write_localized_data(path, dat.inner(), app.lua);
512 }
513 }
514 remove_work_cset();
515 update_any_attrs(app);
516 maybe_update_inodeprints(app);
517 guard.commit();
518}
519
520ALIAS(co, checkout)
521
522CMD(attr, N_("workspace"), N_("set PATH ATTR VALUE\nget PATH [ATTR]\ndrop PATH [ATTR]"),
523 N_("set, get or drop file attributes"),
524 OPT_NONE)
525{
526 if (args.size() < 2 || args.size() > 4)
527 throw usage(name);
528
529 roster_t old_roster, new_roster;
530 temp_node_id_source nis;
531
532 app.require_workspace();
533 get_base_and_current_roster_shape(old_roster, new_roster, nis, app);
534
535
536 file_path path = file_path_external(idx(args,1));
537 split_path sp;
538 path.split(sp);
539
540 N(new_roster.has_node(sp), F("Unknown path '%s'") % path);
541 node_t node = new_roster.get_node(sp);
542
543 string subcmd = idx(args, 0)();
544 if (subcmd == "set" || subcmd == "drop")
545 {
546 if (subcmd == "set")
547 {
548 if (args.size() != 4)
549 throw usage(name);
550
551 attr_key a_key = idx(args, 2)();
552 attr_value a_value = idx(args, 3)();
553
554 node->attrs[a_key] = make_pair(true, a_value);
555 }
556 else
557 {
558 // Clear all attrs (or a specific attr).
559 if (args.size() == 2)
560 {
561 for (full_attr_map_t::iterator i = node->attrs.begin();
562 i != node->attrs.end(); ++i)
563 i->second = make_pair(false, "");
564 }
565 else if (args.size() == 3)
566 {
567 attr_key a_key = idx(args, 2)();
568 N(node->attrs.find(a_key) != node->attrs.end(),
569 F("Path '%s' does not have attribute '%s'\n")
570 % path % a_key);
571 node->attrs[a_key] = make_pair(false, "");
572 }
573 else
574 throw usage(name);
575 }
576
577 cset new_work;
578 make_cset(old_roster, new_roster, new_work);
579 put_work_cset(new_work);
580 update_any_attrs(app);
581 }
582 else if (subcmd == "get")
583 {
584 if (args.size() == 2)
585 {
586 bool has_any_live_attrs = false;
587 for (full_attr_map_t::const_iterator i = node->attrs.begin();
588 i != node->attrs.end(); ++i)
589 if (i->second.first)
590 {
591 cout << path << " : "
592 << i->first << "="
593 << i->second.second << "\n";
594 has_any_live_attrs = true;
595 }
596 if (!has_any_live_attrs)
597 cout << F("No attributes for '%s'") % path << "\n";
598 }
599 else if (args.size() == 3)
600 {
601 attr_key a_key = idx(args, 2)();
602 full_attr_map_t::const_iterator i = node->attrs.find(a_key);
603 if (i != node->attrs.end() && i->second.first)
604 cout << path << " : "
605 << i->first << "="
606 << i->second.second << "\n";
607 else
608 cout << (F("No attribute '%s' on path '%s'")
609 % a_key % path) << "\n";
610 }
611 else
612 throw usage(name);
613 }
614 else
615 throw usage(name);
616}
617
618
619
620CMD(commit, N_("workspace"), N_("[PATH]..."),
621 N_("commit workspace to database"),
622 OPT_BRANCH_NAME % OPT_MESSAGE % OPT_MSGFILE % OPT_DATE %
623 OPT_AUTHOR % OPT_DEPTH % OPT_EXCLUDE)
624{
625 string log_message("");
626 bool log_message_given;
627 revision_t restricted_rev;
628 revision_id old_rev_id, restricted_rev_id;
629 roster_t old_roster, new_roster, restricted_roster;
630 temp_node_id_source nis;
631 cset included, excluded;
632
633 app.make_branch_sticky();
634 app.require_workspace();
635 get_base_and_current_roster_shape(old_roster, new_roster, nis, app);
636
637 node_restriction mask(args_to_paths(args),
638 args_to_paths(app.exclude_patterns),
639 old_roster, new_roster, app);
640
641 update_current_roster_from_filesystem(new_roster, mask, app);
642 make_restricted_csets(old_roster, new_roster,
643 included, excluded, mask);
644 check_restricted_cset(old_roster, included);
645
646 restricted_roster = old_roster;
647 editable_roster_base er(restricted_roster, nis);
648 included.apply_to(er);
649
650 get_revision_id(old_rev_id);
651 make_revision(old_rev_id, old_roster,
652 restricted_roster, restricted_rev);
653
654 calculate_ident(restricted_rev, restricted_rev_id);
655
656 N(restricted_rev.is_nontrivial(), F("no changes to commit"));
657
658 cert_value branchname;
659 I(restricted_rev.edges.size() == 1);
660
661 set<revision_id> heads;
662 get_branch_heads(app.branch_name(), app, heads);
663 unsigned int old_head_size = heads.size();
664
665 if (app.branch_name() != "")
666 branchname = app.branch_name();
667 else
668 guess_branch(edge_old_revision(restricted_rev.edges.begin()), app, branchname);
669
670 P(F("beginning commit on branch '%s'") % branchname);
671 L(FL("new manifest '%s'\n"
672 "new revision '%s'\n")
673 % restricted_rev.new_manifest
674 % restricted_rev_id);
675
676 process_commit_message_args(log_message_given, log_message, app);
677
678 N(!(log_message_given && has_contents_user_log()),
679 F("_MTN/log is non-empty and log message "
680 "was specified on command line\n"
681 "perhaps move or delete _MTN/log,\n"
682 "or remove --message/--message-file from the command line?"));
683
684 if (!log_message_given)
685 {
686 // This call handles _MTN/log.
687
688 get_log_message_interactively(restricted_rev, app, log_message);
689
690 // We only check for empty log messages when the user entered them
691 // interactively. Consensus was that if someone wanted to explicitly
692 // type --message="", then there wasn't any reason to stop them.
693 N(log_message.find_first_not_of("\n\r\t ") != string::npos,
694 F("empty log message; commit canceled"));
695
696 // We save interactively entered log messages to _MTN/log, so if
697 // something goes wrong, the next commit will pop up their old
698 // log message by default. We only do this for interactively
699 // entered messages, because otherwise 'monotone commit -mfoo'
700 // giving an error, means that after you correct that error and
701 // hit up-arrow to try again, you get an "_MTN/log non-empty and
702 // message given on command line" error... which is annoying.
703
704 write_user_log(data(log_message));
705 }
706
707 // If the hook doesn't exist, allow the message to be used.
708 bool message_validated;
709 string reason, new_manifest_text;
710
711 dump(restricted_rev, new_manifest_text);
712
713 app.lua.hook_validate_commit_message(log_message, new_manifest_text,
714 message_validated, reason);
715 N(message_validated, F("log message rejected: %s") % reason);
716
717 {
718 transaction_guard guard(app.db);
719 packet_db_writer dbw(app);
720
721 if (app.db.revision_exists(restricted_rev_id))
722 {
723 W(F("revision %s already in database") % restricted_rev_id);
724 }
725 else
726 {
727 // new revision
728 L(FL("inserting new revision %s") % restricted_rev_id);
729
730 I(restricted_rev.edges.size() == 1);
731 edge_map::const_iterator edge = restricted_rev.edges.begin();
732 I(edge != restricted_rev.edges.end());
733
734 // process file deltas or new files
735 cset const & cs = edge_changes(edge);
736
737 for (map<split_path, pair<file_id, file_id> >::const_iterator
738 i = cs.deltas_applied.begin();
739 i != cs.deltas_applied.end(); ++i)
740 {
741 file_path path(i->first);
742 file_id old_content = i->second.first;
743 file_id new_content = i->second.second;
744
745 if (app.db.file_version_exists(new_content))
746 {
747 L(FL("skipping file delta %s, already in database")
748 % delta_entry_dst(i));
749 }
750 else if (app.db.file_version_exists(old_content))
751 {
752 L(FL("inserting delta %s -> %s")
753 % old_content % new_content);
754 file_data old_data;
755 data new_data;
756 app.db.get_file_version(old_content, old_data);
757 read_localized_data(path, new_data, app.lua);
758 // sanity check
759 hexenc<id> tid;
760 calculate_ident(new_data, tid);
761 N(tid == new_content.inner(),
762 F("file '%s' modified during commit, aborting")
763 % path);
764 delta del;
765 diff(old_data.inner(), new_data, del);
766 dbw.consume_file_delta(old_content,
767 new_content,
768 file_delta(del));
769 }
770 else
771 // If we don't err out here, our packet writer will
772 // later.
773 E(false,
774 F("Your database is missing version %s of file '%s'")
775 % old_content % path);
776 }
777
778 for (map<split_path, file_id>::const_iterator
779 i = cs.files_added.begin();
780 i != cs.files_added.end(); ++i)
781 {
782 file_path path(i->first);
783 file_id new_content = i->second;
784
785 L(FL("inserting full version %s") % new_content);
786 data new_data;
787 read_localized_data(path, new_data, app.lua);
788 // sanity check
789 hexenc<id> tid;
790 calculate_ident(new_data, tid);
791 N(tid == new_content.inner(),
792 F("file '%s' modified during commit, aborting")
793 % path);
794 dbw.consume_file_data(new_content, file_data(new_data));
795 }
796 }
797
798 revision_data rdat;
799 write_revision(restricted_rev, rdat);
800 dbw.consume_revision_data(restricted_rev_id, rdat);
801
802 cert_revision_in_branch(restricted_rev_id, branchname, app, dbw);
803 if (app.date_set)
804 cert_revision_date_time(restricted_rev_id, app.date, app, dbw);
805 else
806 cert_revision_date_now(restricted_rev_id, app, dbw);
807
808 if (app.author().length() > 0)
809 cert_revision_author(restricted_rev_id, app.author(), app, dbw);
810 else
811 cert_revision_author_default(restricted_rev_id, app, dbw);
812
813 cert_revision_changelog(restricted_rev_id, log_message, app, dbw);
814 guard.commit();
815 }
816
817 // small race condition here...
818 put_work_cset(excluded);
819 put_revision_id(restricted_rev_id);
820 P(F("committed revision %s") % restricted_rev_id);
821
822 blank_user_log();
823
824 get_branch_heads(app.branch_name(), app, heads);
825 if (heads.size() > old_head_size && old_head_size > 0) {
826 P(F("note: this revision creates divergence\n"
827 "note: you may (or may not) wish to run '%s merge'")
828 % app.prog_name);
829 }
830
831 update_any_attrs(app);
832 maybe_update_inodeprints(app);
833
834 {
835 // Tell lua what happened. Yes, we might lose some information
836 // here, but it's just an indicator for lua, eg. to post stuff to
837 // a mailing list. If the user *really* cares about cert validity,
838 // multiple certs with same name, etc. they can inquire further,
839 // later.
840 map<cert_name, cert_value> certs;
841 vector< revision<cert> > ctmp;
842 app.db.get_revision_certs(restricted_rev_id, ctmp);
843 for (vector< revision<cert> >::const_iterator i = ctmp.begin();
844 i != ctmp.end(); ++i)
845 {
846 cert_value vtmp;
847 decode_base64(i->inner().value, vtmp);
848 certs.insert(make_pair(i->inner().name, vtmp));
849 }
850 revision_data rdat;
851 app.db.get_revision(restricted_rev_id, rdat);
852 app.lua.hook_note_commit(restricted_rev_id, rdat, certs);
853 }
854}
855
856ALIAS(ci, commit);
857
858
859CMD_NO_WORKSPACE(setup, N_("tree"), N_("[DIRECTORY]"),
860 N_("setup a new workspace directory, default to current"),
861 OPT_BRANCH_NAME)
862{
863 if (args.size() > 1)
864 throw usage(name);
865
866 N(!app.branch_name().empty(), F("need --branch argument for setup"));
867 app.db.ensure_open();
868
869 string dir;
870 if (args.size() == 1)
871 dir = idx(args,0)();
872 else
873 dir = ".";
874
875 app.create_workspace(dir);
876 revision_id null;
877 put_revision_id(null);
878}
879
880CMD(refresh_inodeprints, N_("tree"), "",
881 N_("refresh the inodeprint cache"),
882 OPT_NONE)
883{
884 app.require_workspace();
885 enable_inodeprints();
886 maybe_update_inodeprints(app);
887}
888
889
890// Local Variables:
891// mode: C++
892// fill-column: 76
893// c-file-style: "gnu"
894// indent-tabs-mode: nil
895// End:
896// 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