monotone

monotone Mtn Source Tree

Root/commands.cc

1// copyright (C) 2002, 2003 graydon hoare <graydon@pobox.com>
2// all rights reserved.
3// licensed to the public under the terms of the GNU GPL (>= 2)
4// see the file COPYING for details
5
6#include <map>
7#include <cerrno>
8#include <cstdio>
9#include <cstring>
10#include <set>
11#include <vector>
12#include <algorithm>
13#include <iterator>
14#include <boost/lexical_cast.hpp>
15#include <boost/shared_ptr.hpp>
16#include <boost/tokenizer.hpp>
17#include <boost/filesystem/path.hpp>
18#include <boost/filesystem/convenience.hpp>
19#include <boost/filesystem/exception.hpp>
20
21#include "commands.hh"
22#include "constants.hh"
23
24#include "app_state.hh"
25#include "automate.hh"
26#include "cert.hh"
27#include "database_check.hh"
28#include "diff_patch.hh"
29#include "file_io.hh"
30#include "keys.hh"
31#include "manifest.hh"
32#include "netsync.hh"
33#include "packet.hh"
34#include "rcs_import.hh"
35#include "sanity.hh"
36#include "transforms.hh"
37#include "ui.hh"
38#include "update.hh"
39#include "vocab.hh"
40#include "work.hh"
41#include "automate.hh"
42
43//
44// this file defines the task-oriented "top level" commands which can be
45// issued as part of a monotone command line. the command line can only
46// have one such command on it, followed by a vector of strings which are its
47// arguments. all --options will be processed by the main program *before*
48// calling a command
49//
50// we might expose this blunt command interface to scripting someday. but
51// not today.
52
53namespace commands
54{
55 struct command;
56 bool operator<(command const & self, command const & other);
57};
58
59namespace std
60{
61 template <>
62 struct greater<commands::command *>
63 {
64 bool operator()(commands::command const * a, commands::command const * b)
65 {
66 return *a < *b;
67 }
68 };
69};
70
71namespace commands
72{
73 using namespace std;
74
75 struct command;
76
77 static map<string,command *> cmds;
78
79 struct command
80 {
81 string name;
82 string cmdgroup;
83 string params;
84 string desc;
85 command(string const & n,
86 string const & g,
87 string const & p,
88 string const & d) : name(n), cmdgroup(g), params(p), desc(d)
89 { cmds[n] = this; }
90 virtual ~command() {}
91 virtual void exec(app_state & app, vector<utf8> const & args) = 0;
92 };
93
94 bool operator<(command const & self, command const & other)
95 {
96 return ((self.cmdgroup < other.cmdgroup)
97 || ((self.cmdgroup == other.cmdgroup) && (self.name < other.name)));
98 }
99
100
101 string complete_command(string const & cmd)
102 {
103 if (cmd.length() == 0 || cmds.find(cmd) != cmds.end()) return cmd;
104
105 P(F("expanding command '%s'\n") % cmd);
106
107 vector<string> matched;
108
109 for (map<string,command *>::const_iterator i = cmds.begin();
110 i != cmds.end(); ++i)
111 {
112 if (cmd.length() < i->first.length())
113 {
114 string prefix(i->first, 0, cmd.length());
115 if (cmd == prefix) matched.push_back(i->first);
116 }
117 }
118
119 if (matched.size() == 1)
120 {
121 string completed = *matched.begin();
122 P(F("expanded command to '%s'\n") % completed);
123 return completed;
124 }
125 else if (matched.size() > 1)
126 {
127 string err = (F("command '%s' has multiple ambiguous expansions: \n") % cmd).str();
128 for (vector<string>::iterator i = matched.begin();
129 i != matched.end(); ++i)
130 err += (*i + "\n");
131 W(boost::format(err));
132 }
133
134 return cmd;
135 }
136
137 void explain_usage(string const & cmd, ostream & out)
138 {
139 map<string,command *>::const_iterator i;
140
141 string completed = complete_command(cmd);
142
143 // try to get help on a specific command
144
145 i = cmds.find(completed);
146
147 if (i != cmds.end())
148 {
149 string params = i->second->params;
150 vector<string> lines;
151 split_into_lines(params, lines);
152 for (vector<string>::const_iterator j = lines.begin();
153 j != lines.end(); ++j)
154 out << " " << i->second->name << " " << *j << endl;
155 split_into_lines(i->second->desc, lines);
156 for (vector<string>::const_iterator j = lines.begin();
157 j != lines.end(); ++j)
158 out << " " << *j << endl;
159 out << endl;
160 return;
161 }
162
163 vector<command *> sorted;
164 out << "commands:" << endl;
165 for (i = cmds.begin(); i != cmds.end(); ++i)
166 {
167 sorted.push_back(i->second);
168 }
169
170 sort(sorted.begin(), sorted.end(), std::greater<command *>());
171
172 string curr_group;
173 size_t col = 0;
174 size_t col2 = 0;
175 for (size_t i = 0; i < sorted.size(); ++i)
176 {
177 col2 = col2 > idx(sorted, i)->cmdgroup.size() ? col2 : idx(sorted, i)->cmdgroup.size();
178 }
179
180 for (size_t i = 0; i < sorted.size(); ++i)
181 {
182 if (idx(sorted, i)->cmdgroup != curr_group)
183 {
184 curr_group = idx(sorted, i)->cmdgroup;
185 out << endl;
186 out << " " << idx(sorted, i)->cmdgroup;
187 col = idx(sorted, i)->cmdgroup.size() + 2;
188 while (col++ < (col2 + 3))
189 out << ' ';
190 }
191 out << " " << idx(sorted, i)->name;
192 col += idx(sorted, i)->name.size() + 1;
193 if (col >= 70)
194 {
195 out << endl;
196 col = 0;
197 while (col++ < (col2 + 3))
198 out << ' ';
199 }
200 }
201 out << endl << endl;
202 }
203
204 int process(app_state & app, string const & cmd, vector<utf8> const & args)
205 {
206 string completed = complete_command(cmd);
207
208 if (cmds.find(completed) != cmds.end())
209 {
210 L(F("executing %s command\n") % completed);
211 cmds[completed]->exec(app, args);
212 return 0;
213 }
214 else
215 {
216 ui.inform(F("unknown command '%s'\n") % cmd);
217 return 1;
218 }
219 }
220
221#define CMD(C, group, params, desc) \
222struct cmd_ ## C : public command \
223{ \
224 cmd_ ## C() : command(#C, group, params, desc) \
225 {} \
226 virtual void exec(app_state & app, \
227 vector<utf8> const & args); \
228}; \
229static cmd_ ## C C ## _cmd; \
230void cmd_ ## C::exec(app_state & app, \
231 vector<utf8> const & args) \
232
233#define ALIAS(C, realcommand, group, params, desc) \
234CMD(C, group, params, desc) \
235{ \
236 process(app, string(#realcommand), args); \
237}
238
239static void
240get_work_path(local_path & w_path)
241{
242 w_path = (mkpath(book_keeping_dir) / mkpath(work_file_name)).string();
243 L(F("work path is %s\n") % w_path);
244}
245
246static void
247get_revision_path(local_path & m_path)
248{
249 m_path = (mkpath(book_keeping_dir) / mkpath(revision_file_name)).string();
250 L(F("revision path is %s\n") % m_path);
251}
252
253static void
254get_revision_id(revision_id & c)
255{
256 c = revision_id();
257 local_path c_path;
258 get_revision_path(c_path);
259
260 N(file_exists(c_path),
261 F("working copy is corrupt: %s does not exist\n") % c_path);
262
263 data c_data;
264 L(F("loading revision id from %s\n") % c_path);
265 read_data(c_path, c_data);
266 c = revision_id(remove_ws(c_data()));
267}
268
269static void
270put_revision_id(revision_id & rev)
271{
272 local_path c_path;
273 get_revision_path(c_path);
274 L(F("writing revision id to %s\n") % c_path);
275 data c_data(rev.inner()() + "\n");
276 write_data(c_path, c_data);
277}
278
279static void
280get_path_rearrangement(change_set::path_rearrangement & w)
281{
282 local_path w_path;
283 get_work_path(w_path);
284 if (file_exists(w_path))
285 {
286 L(F("checking for un-committed work file %s\n") % w_path);
287 data w_data;
288 read_data(w_path, w_data);
289 read_path_rearrangement(w_data, w);
290 L(F("read rearrangement from %s\n") % w_path);
291 }
292 else
293 {
294 L(F("no un-committed work file %s\n") % w_path);
295 }
296}
297
298static void
299remove_path_rearrangement()
300{
301 local_path w_path;
302 get_work_path(w_path);
303 if (file_exists(w_path))
304 delete_file(w_path);
305}
306
307static void
308put_path_rearrangement(change_set::path_rearrangement & w)
309{
310 local_path w_path;
311 get_work_path(w_path);
312
313 if (w.empty())
314 {
315 if (file_exists(w_path))
316 delete_file(w_path);
317 }
318 else
319 {
320 data w_data;
321 write_path_rearrangement(w, w_data);
322 write_data(w_path, w_data);
323 }
324}
325
326static void
327extract_rearranged_paths(change_set::path_rearrangement const & rearrangement, path_set & paths)
328{
329 paths.insert(rearrangement.deleted_files.begin(), rearrangement.deleted_files.end());
330 paths.insert(rearrangement.deleted_dirs.begin(), rearrangement.deleted_dirs.end());
331
332 for (map<file_path, file_path>::const_iterator i = rearrangement.renamed_files.begin();
333 i != rearrangement.renamed_files.end(); ++i)
334 {
335 paths.insert(i->first);
336 paths.insert(i->second);
337 }
338
339 for (map<file_path, file_path>::const_iterator i = rearrangement.renamed_dirs.begin();
340 i != rearrangement.renamed_dirs.end(); ++i)
341 {
342 paths.insert(i->first);
343 paths.insert(i->second);
344 }
345
346 paths.insert(rearrangement.added_files.begin(), rearrangement.added_files.end());
347}
348
349static void
350extract_delta_paths(change_set::delta_map const & deltas, path_set & paths)
351{
352 for (change_set::delta_map::const_iterator i = deltas.begin(); i != deltas.end(); ++i)
353 {
354 paths.insert(i->first);
355 }
356}
357
358static void
359extract_changed_paths(change_set const & cs, path_set & paths)
360{
361 extract_rearranged_paths(cs.rearrangement, paths);
362 extract_delta_paths(cs.deltas, paths);
363}
364
365static void
366add_intermediate_paths(path_set & paths)
367{
368 path_set intermediate_paths;
369
370 for (path_set::const_iterator i = paths.begin(); i != paths.end(); ++i)
371 {
372 fs::path p = mkpath((*i)());
373 while (p.has_branch_path())
374 {
375 p = p.branch_path();
376 file_path dir(p.string());
377
378 // once we hit a subdir that exists or has been added we're done.
379 if (paths.find(dir) != paths.end()) break;
380 if (intermediate_paths.find(dir) != intermediate_paths.end()) break;
381
382 intermediate_paths.insert(dir);
383 }
384 }
385
386 paths.insert(intermediate_paths.begin(), intermediate_paths.end());
387}
388
389
390static void
391restrict_path_set(string const & type,
392 path_set const & paths,
393 path_set & included,
394 path_set & excluded,
395 app_state & app)
396{
397 for (path_set::const_iterator i = paths.begin(); i != paths.end(); ++i)
398 {
399 if (app.restriction_includes(*i))
400 {
401 L(F("restriction includes %s %s\n") % type % *i);
402 included.insert(*i);
403 }
404 else
405 {
406 L(F("restriction excludes %s %s\n") % type % *i);
407 excluded.insert(*i);
408 }
409 }
410}
411
412static void
413restrict_rename_set(string const & type,
414 std::map<file_path, file_path> const & renames,
415 std::map<file_path, file_path> & included,
416 std::map<file_path, file_path> & excluded,
417 app_state & app)
418{
419 for (std::map<file_path, file_path>::const_iterator i = renames.begin();
420 i != renames.end(); ++i)
421 {
422 // include renames if either source or target name is included in the restriction
423 if (app.restriction_includes(i->first) || app.restriction_includes(i->second))
424 {
425 L(F("restriction includes %s '%s' to '%s'\n") % type % i->first % i->second);
426 included.insert(*i);
427 }
428 else
429 {
430 L(F("restriction excludes %s '%s' to '%s'\n") % type % i->first % i->second);
431 excluded.insert(*i);
432 }
433 }
434}
435
436static void
437restrict_path_rearrangement(change_set::path_rearrangement const & work,
438 change_set::path_rearrangement & included,
439 change_set::path_rearrangement & excluded,
440 app_state & app)
441{
442 restrict_path_set("delete file", work.deleted_files,
443 included.deleted_files, excluded.deleted_files, app);
444 restrict_path_set("delete dir", work.deleted_dirs,
445 included.deleted_dirs, excluded.deleted_dirs, app);
446
447 restrict_rename_set("rename file", work.renamed_files,
448 included.renamed_files, excluded.renamed_files, app);
449 restrict_rename_set("rename dir", work.renamed_dirs,
450 included.renamed_dirs, excluded.renamed_dirs, app);
451
452 restrict_path_set("add file", work.added_files,
453 included.added_files, excluded.added_files, app);
454}
455
456static void
457restrict_delta_map(change_set::delta_map const & deltas,
458 change_set::delta_map & included,
459 change_set::delta_map & excluded,
460 app_state & app)
461{
462 for (change_set::delta_map::const_iterator i = deltas.begin(); i!= deltas.end(); ++i)
463 {
464 if (app.restriction_includes(i->first))
465 {
466 L(F("restriction includes delta on %s\n") % i->first);
467 included.insert(*i);
468 }
469 else
470 {
471 L(F("restriction excludes delta on %s\n") % i->first);
472 excluded.insert(*i);
473 }
474 }
475}
476
477static void
478update_any_attrs(app_state & app)
479{
480 file_path fp;
481 data attr_data;
482 attr_map attr;
483
484 get_attr_path(fp);
485 if (!file_exists(fp))
486 return;
487
488 read_data(fp, attr_data);
489 read_attr_map(attr_data, attr);
490 apply_attributes(app, attr);
491}
492
493static void
494calculate_base_revision(app_state & app,
495 revision_id & rid,
496 manifest_id & mid,
497 manifest_map & man)
498{
499 man.clear();
500
501 get_revision_id(rid);
502
503 if (!null_id(rid))
504 {
505
506 N(app.db.revision_exists(rid),
507 F("base revision %s does not exist in database\n") % rid);
508
509 app.db.get_revision_manifest(rid, mid);
510 L(F("old manifest is %s\n") % mid);
511
512 N(app.db.manifest_version_exists(mid),
513 F("base manifest %s does not exist in database\n") % mid);
514
515 app.db.get_manifest(mid, man);
516 }
517
518 L(F("old manifest has %d entries\n") % man.size());
519}
520
521static void
522calculate_base_manifest(app_state & app,
523 manifest_map & man)
524{
525 revision_id rid;
526 manifest_id mid;
527 calculate_base_revision(app, rid, mid, man);
528}
529
530static void
531calculate_current_revision(app_state & app,
532 revision_set & rev,
533 manifest_map & m_old,
534 manifest_map & m_new)
535{
536 manifest_id old_manifest_id;
537 revision_id old_revision_id;
538 boost::shared_ptr<change_set> cs(new change_set());
539 path_set old_paths, new_paths;
540
541 rev.edges.clear();
542 m_old.clear();
543 m_new.clear();
544
545 calculate_base_revision(app,
546 old_revision_id,
547 old_manifest_id, m_old);
548
549
550 get_path_rearrangement(cs->rearrangement);
551 extract_path_set(m_old, old_paths);
552 apply_path_rearrangement(old_paths, cs->rearrangement, new_paths);
553 build_manifest_map(new_paths, m_new, app);
554 complete_change_set(m_old, m_new, *cs);
555
556 calculate_ident(m_new, rev.new_manifest);
557 L(F("new manifest is %s\n") % rev.new_manifest);
558
559 rev.edges.insert(make_pair(old_revision_id,
560 make_pair(old_manifest_id, cs)));
561}
562
563static void
564calculate_restricted_revision(app_state & app,
565 vector<utf8> const & args,
566 revision_set & rev,
567 manifest_map & m_old,
568 manifest_map & m_new,
569 change_set::path_rearrangement & restricted_work)
570{
571 manifest_id old_manifest_id;
572 revision_id old_revision_id;
573 boost::shared_ptr<change_set> cs(new change_set());
574 path_set old_paths, new_paths;
575 change_set::path_rearrangement work, included, excluded;
576
577 rev.edges.clear();
578 m_old.clear();
579 m_new.clear();
580
581 calculate_base_revision(app,
582 old_revision_id,
583 old_manifest_id, m_old);
584
585 get_path_rearrangement(work);
586 extract_path_set(m_old, old_paths);
587
588 path_set valid_paths(old_paths);
589 extract_rearranged_paths(work, valid_paths);
590 add_intermediate_paths(valid_paths);
591
592 app.set_restriction(valid_paths, args);
593
594 restrict_path_rearrangement(work, included, excluded, app);
595
596 apply_path_rearrangement(old_paths, included, new_paths);
597
598 cs->rearrangement = included;
599 restricted_work = excluded;
600
601 build_restricted_manifest_map(new_paths, m_old, m_new, app);
602 complete_change_set(m_old, m_new, *cs);
603
604 calculate_ident(m_new, rev.new_manifest);
605 L(F("new manifest is %s\n") % rev.new_manifest);
606
607 rev.edges.insert(make_pair(old_revision_id,
608 make_pair(old_manifest_id, cs)));
609}
610
611static void
612calculate_restricted_revision(app_state & app,
613 vector<utf8> const & args,
614 revision_set & rev,
615 manifest_map & m_old,
616 manifest_map & m_new)
617{
618 change_set::path_rearrangement work;
619 calculate_restricted_revision(app, args, rev, m_old, m_new, work);
620}
621
622static void
623calculate_restricted_change_set(app_state & app,
624 vector<utf8> const & args,
625 change_set const & cs,
626 change_set & included,
627 change_set & excluded)
628{
629 path_set valid_paths;
630
631 extract_changed_paths(cs, valid_paths);
632 add_intermediate_paths(valid_paths);
633
634 app.set_restriction(valid_paths, args);
635
636 restrict_path_rearrangement(cs.rearrangement,
637 included.rearrangement, excluded.rearrangement, app);
638
639 restrict_delta_map(cs.deltas, included.deltas, excluded.deltas, app);
640}
641
642static string
643get_stdin()
644{
645 char buf[constants::bufsz];
646 string tmp;
647 while(cin)
648 {
649 cin.read(buf, constants::bufsz);
650 tmp.append(buf, cin.gcount());
651 }
652 return tmp;
653}
654
655static void
656get_log_message(revision_set const & cs,
657 app_state & app,
658 string & log_message)
659{
660 string commentary;
661 data summary, user_log_message;
662 write_revision_set(cs, summary);
663 read_user_log(user_log_message);
664 commentary += "----------------------------------------------------------------------\n";
665 commentary += "Enter Log. Lines beginning with `MT:' are removed automatically\n";
666 commentary += "\n";
667 commentary += summary();
668 commentary += "----------------------------------------------------------------------\n";
669 N(app.lua.hook_edit_comment(commentary, user_log_message(), log_message),
670 F("edit of log message failed"));
671}
672
673static string
674describe_revision(app_state & app, revision_id const & id)
675{
676 cert_name author_name(author_cert_name);
677 cert_name date_name(date_cert_name);
678
679 string description;
680
681 description += id.inner()();
682
683 // append authors and date of this revision
684 vector< revision<cert> > tmp;
685 app.db.get_revision_certs(id, author_name, tmp);
686 erase_bogus_certs(tmp, app);
687 for (vector< revision<cert> >::const_iterator i = tmp.begin();
688 i != tmp.end(); ++i)
689 {
690 cert_value tv;
691 decode_base64(i->inner().value, tv);
692 description += " ";
693 description += tv();
694 }
695 app.db.get_revision_certs(id, date_name, tmp);
696 erase_bogus_certs(tmp, app);
697 for (vector< revision<cert> >::const_iterator i = tmp.begin();
698 i != tmp.end(); ++i)
699 {
700 cert_value tv;
701 decode_base64(i->inner().value, tv);
702 description += " ";
703 description += tv();
704 }
705
706 return description;
707}
708
709static void
710decode_selector(string const & orig_sel,
711 selector_type & type,
712 string & sel,
713 app_state & app)
714{
715 sel = orig_sel;
716
717 L(F("decoding selector '%s'\n") % sel);
718
719 if (sel.size() < 2 || sel[1] != ':')
720 {
721 string tmp;
722 if (!app.lua.hook_expand_selector(sel, tmp))
723 {
724 L(F("expansion of selector '%s' failed\n") % sel);
725 }
726 else
727 {
728 P(F("expanded selector '%s' -> '%s'\n") % sel % tmp);
729 sel = tmp;
730 }
731 }
732
733 if (sel.size() >= 2 && sel[1] == ':')
734 {
735 switch (sel[0])
736 {
737 case 'a':
738 type = sel_author;
739 break;
740 case 'b':
741 type = sel_branch;
742 break;
743 case 'd':
744 type = sel_date;
745 break;
746 case 'i':
747 type = sel_ident;
748 break;
749 case 't':
750 type = sel_tag;
751 break;
752 default:
753 W(F("unknown selector type: %c\n") % sel[0]);
754 break;
755 }
756 sel.erase(0,2);
757 }
758}
759
760static void
761complete_selector(string const & orig_sel,
762 vector<pair<selector_type, string> > const & limit,
763 selector_type & type,
764 set<string> & completions,
765 app_state & app)
766{
767 string sel;
768 decode_selector(orig_sel, type, sel, app);
769 app.db.complete(type, sel, limit, completions);
770}
771
772
773static void
774complete(app_state & app,
775 string const & str,
776 revision_id & completion)
777{
778
779 // this rule should always be enabled, even if the user specifies
780 // --norc: if you provide a revision id, you get a revision id.
781 if (str.find_first_not_of(constants::legal_id_bytes) == string::npos
782 && str.size() == constants::idlen)
783 {
784 completion = revision_id(str);
785 return;
786 }
787
788 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
789 boost::char_separator<char> slash("/");
790 tokenizer tokens(str, slash);
791
792 vector<string> selector_strings;
793 vector<pair<selector_type, string> > selectors;
794 copy(tokens.begin(), tokens.end(), back_inserter(selector_strings));
795 for (vector<string>::const_iterator i = selector_strings.begin();
796 i != selector_strings.end(); ++i)
797 {
798 string sel;
799 selector_type type = sel_unknown;
800 decode_selector(*i, type, sel, app);
801 selectors.push_back(make_pair(type, sel));
802 }
803
804 P(F("expanding selection '%s'\n") % str);
805
806 // we jam through an "empty" selection on sel_ident type
807 set<string> completions;
808 selector_type ty = sel_ident;
809 complete_selector("", selectors, ty, completions, app);
810
811 N(completions.size() != 0,
812 F("no match for selection '%s'") % str);
813 if (completions.size() > 1)
814 {
815 string err = (F("selection '%s' has multiple ambiguous expansions: \n") % str).str();
816 for (set<string>::const_iterator i = completions.begin();
817 i != completions.end(); ++i)
818 err += (describe_revision(app, revision_id(*i)) + "\n");
819 N(completions.size() == 1, boost::format(err));
820 }
821 completion = revision_id(*(completions.begin()));
822 P(F("expanded to '%s'\n") % completion);
823}
824
825static void
826complete(app_state & app,
827 string const & str,
828 manifest_id & completion)
829{
830 N(str.find_first_not_of(constants::legal_id_bytes) == string::npos,
831 F("non-hex digits in id"));
832 if (str.size() == constants::idlen)
833 {
834 completion = manifest_id(str);
835 return;
836 }
837 set<manifest_id> completions;
838 app.db.complete(str, completions);
839 N(completions.size() != 0,
840 F("partial id '%s' does not have a unique expansion") % str);
841 if (completions.size() > 1)
842 {
843 string err = (F("partial id '%s' has multiple ambiguous expansions: \n") % str).str();
844 for (set<manifest_id>::const_iterator i = completions.begin();
845 i != completions.end(); ++i)
846 err += (i->inner()() + "\n");
847 N(completions.size() == 1, boost::format(err));
848 }
849 completion = *(completions.begin());
850 P(F("expanding partial id '%s'\n") % str);
851 P(F("expanded to '%s'\n") % completion);
852}
853
854static void
855complete(app_state & app,
856 string const & str,
857 file_id & completion)
858{
859 N(str.find_first_not_of(constants::legal_id_bytes) == string::npos,
860 F("non-hex digits in id"));
861 if (str.size() == constants::idlen)
862 {
863 completion = file_id(str);
864 return;
865 }
866 set<file_id> completions;
867 app.db.complete(str, completions);
868 N(completions.size() != 0,
869 F("partial id '%s' does not have a unique expansion") % str);
870 if (completions.size() > 1)
871 {
872 string err = (F("partial id '%s' has multiple ambiguous expansions: \n") % str).str();
873 for (set<file_id>::const_iterator i = completions.begin();
874 i != completions.end(); ++i)
875 err += (i->inner()() + "\n");
876 N(completions.size() == 1, boost::format(err));
877 }
878 completion = *(completions.begin());
879 P(F("expanding partial id '%s'\n") % str);
880 P(F("expanded to '%s'\n") % completion);
881}
882
883static void
884ls_certs(string const & name, app_state & app, vector<utf8> const & args)
885{
886 if (args.size() != 1)
887 throw usage(name);
888
889 vector<cert> certs;
890
891 transaction_guard guard(app.db);
892
893 revision_id ident;
894 complete(app, idx(args, 0)(), ident);
895 vector< revision<cert> > ts;
896 app.db.get_revision_certs(ident, ts);
897 for (size_t i = 0; i < ts.size(); ++i)
898 certs.push_back(idx(ts, i).inner());
899
900 {
901 set<rsa_keypair_id> checked;
902 for (size_t i = 0; i < certs.size(); ++i)
903 {
904 if (checked.find(idx(certs, i).key) == checked.end() &&
905 !app.db.public_key_exists(idx(certs, i).key))
906 P(F("warning: no public key '%s' found in database\n")
907 % idx(certs, i).key);
908 checked.insert(idx(certs, i).key);
909 }
910 }
911
912 // Make the output deterministic; this is useful for the test suite, in
913 // particular.
914 sort(certs.begin(), certs.end());
915
916 for (size_t i = 0; i < certs.size(); ++i)
917 {
918 cert_status status = check_cert(app, idx(certs, i));
919 cert_value tv;
920 decode_base64(idx(certs, i).value, tv);
921 string washed;
922 if (guess_binary(tv()))
923 {
924 washed = "<binary data>";
925 }
926 else
927 {
928 washed = tv();
929 }
930
931 string stat;
932 switch (status)
933 {
934 case cert_ok:
935 stat = "ok";
936 break;
937 case cert_bad:
938 stat = "bad";
939 break;
940 case cert_unknown:
941 stat = "unknown";
942 break;
943 }
944
945 vector<string> lines;
946 split_into_lines(washed, lines);
947 I(lines.size() > 0);
948
949 cout << "-----------------------------------------------------------------" << endl
950 << "Key : " << idx(certs, i).key() << endl
951 << "Sig : " << stat << endl
952 << "Name : " << idx(certs, i).name() << endl
953 << "Value : " << idx(lines, 0) << endl;
954
955 for (size_t i = 1; i < lines.size(); ++i)
956 cout << " : " << idx(lines, i) << endl;
957 }
958
959 if (certs.size() > 0)
960 cout << endl;
961
962 guard.commit();
963}
964
965static void
966ls_keys(string const & name, app_state & app, vector<utf8> const & args)
967{
968 vector<rsa_keypair_id> pubkeys;
969 vector<rsa_keypair_id> privkeys;
970
971 transaction_guard guard(app.db);
972
973 if (args.size() == 0)
974 app.db.get_key_ids("", pubkeys, privkeys);
975 else if (args.size() == 1)
976 app.db.get_key_ids(idx(args, 0)(), pubkeys, privkeys);
977 else
978 throw usage(name);
979
980 if (pubkeys.size() > 0)
981 {
982 cout << endl << "[public keys]" << endl;
983 for (size_t i = 0; i < pubkeys.size(); ++i)
984 {
985 rsa_keypair_id keyid = idx(pubkeys, i)();
986 base64<rsa_pub_key> pub_encoded;
987 hexenc<id> hash_code;
988
989 app.db.get_key(keyid, pub_encoded);
990 key_hash_code(keyid, pub_encoded, hash_code);
991 cout << hash_code << " " << keyid << endl;
992 }
993 cout << endl;
994 }
995
996 if (privkeys.size() > 0)
997 {
998 cout << endl << "[private keys]" << endl;
999 for (size_t i = 0; i < privkeys.size(); ++i)
1000 {
1001 rsa_keypair_id keyid = idx(privkeys, i)();
1002 base64< arc4<rsa_priv_key> > priv_encoded;
1003 hexenc<id> hash_code;
1004 app.db.get_key(keyid, priv_encoded);
1005 key_hash_code(keyid, priv_encoded, hash_code);
1006 cout << hash_code << " " << keyid << endl;
1007 }
1008 cout << endl;
1009 }
1010
1011 if (pubkeys.size() == 0 &&
1012 privkeys.size() == 0)
1013 {
1014 if (args.size() == 0)
1015 P(F("no keys found\n"));
1016 else
1017 W(F("no keys found matching '%s'\n") % idx(args, 0)());
1018 }
1019}
1020
1021// The changes_summary structure holds a list all of files and directories
1022// affected in a revision, and is useful in the 'log' command to print this
1023// information easily. It has to be constructed from all change_set objects
1024// that belong to a revision.
1025struct
1026changes_summary
1027{
1028 bool empty;
1029 change_set::path_rearrangement rearrangement;
1030 std::set<file_path> modified_files;
1031
1032 changes_summary(void);
1033 void add_change_set(change_set const & cs);
1034 void print(std::ostream & os, size_t max_cols) const;
1035};
1036
1037changes_summary::changes_summary(void) : empty(true)
1038{
1039}
1040
1041void
1042changes_summary::add_change_set(change_set const & cs)
1043{
1044 if (cs.empty())
1045 return;
1046 empty = false;
1047
1048 change_set::path_rearrangement const & pr = cs.rearrangement;
1049
1050 for (std::set<file_path>::const_iterator i = pr.deleted_files.begin();
1051 i != pr.deleted_files.end(); i++)
1052 rearrangement.deleted_files.insert(*i);
1053
1054 for (std::set<file_path>::const_iterator i = pr.deleted_dirs.begin();
1055 i != pr.deleted_dirs.end(); i++)
1056 rearrangement.deleted_dirs.insert(*i);
1057
1058 for (std::map<file_path, file_path>::const_iterator
1059 i = pr.renamed_files.begin(); i != pr.renamed_files.end(); i++)
1060 rearrangement.renamed_files.insert(*i);
1061
1062 for (std::map<file_path, file_path>::const_iterator
1063 i = pr.renamed_dirs.begin(); i != pr.renamed_dirs.end(); i++)
1064 rearrangement.renamed_dirs.insert(*i);
1065
1066 for (std::set<file_path>::const_iterator i = pr.added_files.begin();
1067 i != pr.added_files.end(); i++)
1068 rearrangement.added_files.insert(*i);
1069
1070 for (change_set::delta_map::const_iterator i = cs.deltas.begin();
1071 i != cs.deltas.end(); i++)
1072 {
1073 if (pr.added_files.find(i->first()) == pr.added_files.end())
1074 modified_files.insert(i->first());
1075 }
1076}
1077
1078void
1079changes_summary::print(std::ostream & os, size_t max_cols) const
1080{
1081#define PRINT_INDENTED_SET(setname) \
1082 size_t cols = 8; \
1083 os << " "; \
1084 for (std::set<file_path>::const_iterator i = setname.begin(); \
1085 i != setname.end(); i++) \
1086 { \
1087 const std::string str = (*i)(); \
1088 if (cols > 8 && cols + str.size() + 1 >= max_cols) \
1089 { \
1090 cols = 8; \
1091 os << endl << " "; \
1092 } \
1093 os << " " << str; \
1094 cols += str.size() + 1; \
1095 } \
1096 os << endl;
1097
1098 if (! rearrangement.deleted_files.empty())
1099 {
1100 os << "Deleted files:" << endl;
1101 PRINT_INDENTED_SET(rearrangement.deleted_files)
1102 }
1103
1104 if (! rearrangement.deleted_dirs.empty())
1105 {
1106 os << "Deleted directories:" << endl;
1107 PRINT_INDENTED_SET(rearrangement.deleted_dirs)
1108 }
1109
1110 if (! rearrangement.renamed_files.empty())
1111 {
1112 os << "Renamed files:" << endl;
1113 for (std::map<file_path, file_path>::const_iterator
1114 i = rearrangement.renamed_files.begin();
1115 i != rearrangement.renamed_files.end(); i++)
1116 os << " " << i->first << " to " << i->second << endl;
1117 }
1118
1119 if (! rearrangement.renamed_dirs.empty())
1120 {
1121 os << "Renamed directories:" << endl;
1122 for (std::map<file_path, file_path>::const_iterator
1123 i = rearrangement.renamed_dirs.begin();
1124 i != rearrangement.renamed_dirs.end(); i++)
1125 os << " " << i->first << " to " << i->second << endl;
1126 }
1127
1128 if (! rearrangement.added_files.empty())
1129 {
1130 os << "Added files:" << endl;
1131 PRINT_INDENTED_SET(rearrangement.added_files)
1132 }
1133
1134 if (! modified_files.empty())
1135 {
1136 os << "Modified files:" << endl;
1137 PRINT_INDENTED_SET(modified_files)
1138 }
1139
1140#undef PRINT_INDENTED_SET
1141}
1142
1143CMD(genkey, "key and cert", "KEYID", "generate an RSA key-pair")
1144{
1145 if (args.size() != 1)
1146 throw usage(name);
1147
1148 transaction_guard guard(app.db);
1149 rsa_keypair_id ident;
1150 internalize_rsa_keypair_id(idx(args, 0), ident);
1151
1152 N(! app.db.key_exists(ident),
1153 F("key '%s' already exists in database") % ident);
1154
1155 base64<rsa_pub_key> pub;
1156 base64< arc4<rsa_priv_key> > priv;
1157 P(F("generating key-pair '%s'\n") % ident);
1158 generate_key_pair(app.lua, ident, pub, priv);
1159 P(F("storing key-pair '%s' in database\n") % ident);
1160 app.db.put_key_pair(ident, pub, priv);
1161
1162 guard.commit();
1163}
1164
1165CMD(dropkey, "key and cert", "KEYID", "drop a public and private key")
1166{
1167 bool key_deleted = false;
1168
1169 if (args.size() != 1)
1170 throw usage(name);
1171
1172 transaction_guard guard(app.db);
1173 rsa_keypair_id ident(idx(args, 0)());
1174 if (app.db.public_key_exists(ident))
1175 {
1176 P(F("dropping public key '%s' from database\n") % ident);
1177 app.db.delete_public_key(ident);
1178 key_deleted = true;
1179 }
1180
1181 if (app.db.private_key_exists(ident))
1182 {
1183 P(F("dropping private key '%s' from database\n") % ident);
1184 app.db.delete_private_key(ident);
1185 key_deleted = true;
1186 }
1187
1188 N(key_deleted,
1189 F("public or private key '%s' does not exist in database") % idx(args, 0)());
1190
1191 guard.commit();
1192}
1193
1194CMD(chkeypass, "key and cert", "KEYID", "change passphrase of a private RSA key")
1195{
1196 if (args.size() != 1)
1197 throw usage(name);
1198
1199 transaction_guard guard(app.db);
1200 rsa_keypair_id ident;
1201 internalize_rsa_keypair_id(idx(args, 0), ident);
1202
1203 N(app.db.key_exists(ident),
1204 F("key '%s' does not exist in database") % ident);
1205
1206 base64< arc4<rsa_priv_key> > key;
1207 app.db.get_key(ident, key);
1208 change_key_passphrase(app.lua, ident, key);
1209 app.db.delete_private_key(ident);
1210 app.db.put_key(ident, key);
1211 P(F("passphrase changed\n"));
1212
1213 guard.commit();
1214}
1215
1216CMD(cert, "key and cert", "REVISION CERTNAME [CERTVAL]",
1217 "create a cert for a revision")
1218{
1219 if ((args.size() != 3) && (args.size() != 2))
1220 throw usage(name);
1221
1222 transaction_guard guard(app.db);
1223
1224 hexenc<id> ident;
1225 revision_id rid;
1226 complete(app, idx(args, 0)(), rid);
1227 ident = rid.inner();
1228
1229 cert_name name;
1230 internalize_cert_name(idx(args, 1), name);
1231
1232 rsa_keypair_id key;
1233 if (app.signing_key() != "")
1234 key = app.signing_key;
1235 else
1236 N(guess_default_key(key, app),
1237 F("no unique private key found, and no key specified"));
1238
1239 cert_value val;
1240 if (args.size() == 3)
1241 val = cert_value(idx(args, 2)());
1242 else
1243 val = cert_value(get_stdin());
1244
1245 base64<cert_value> val_encoded;
1246 encode_base64(val, val_encoded);
1247
1248 cert t(ident, name, val_encoded, key);
1249
1250 packet_db_writer dbw(app);
1251 calculate_cert(app, t);
1252 dbw.consume_revision_cert(revision<cert>(t));
1253 guard.commit();
1254}
1255
1256CMD(trusted, "key and cert", "REVISION NAME VALUE SIGNER1 [SIGNER2 [...]]",
1257 "test whether a hypothetical cert would be trusted\n"
1258 "by current settings")
1259{
1260 if (args.size() < 4)
1261 throw usage(name);
1262
1263 revision_id rid;
1264 complete(app, idx(args, 0)(), rid);
1265 hexenc<id> ident(rid.inner());
1266
1267 cert_name name;
1268 internalize_cert_name(idx(args, 1), name);
1269
1270 cert_value value(idx(args, 2)());
1271
1272 set<rsa_keypair_id> signers;
1273 for (unsigned int i = 3; i != args.size(); ++i)
1274 {
1275 rsa_keypair_id keyid;
1276 internalize_rsa_keypair_id(idx(args, i), keyid);
1277 signers.insert(keyid);
1278 }
1279
1280
1281 bool trusted = app.lua.hook_get_revision_cert_trust(signers, ident,
1282 name, value);
1283
1284 cout << "if a cert on: " << ident << endl
1285 << "with key: " << name << endl
1286 << "and value: " << value << endl
1287 << "was signed by: ";
1288 for (set<rsa_keypair_id>::const_iterator i = signers.begin(); i != signers.end(); ++i)
1289 cout << *i << " ";
1290 cout << endl
1291 << "it would be: " << (trusted ? "trusted" : "UNtrusted") << endl;
1292}
1293
1294CMD(tag, "review", "REVISION TAGNAME",
1295 "put a symbolic tag cert on a revision version")
1296{
1297 if (args.size() != 2)
1298 throw usage(name);
1299
1300 revision_id r;
1301 complete(app, idx(args, 0)(), r);
1302 packet_db_writer dbw(app);
1303 cert_revision_tag(r, idx(args, 1)(), app, dbw);
1304}
1305
1306
1307CMD(testresult, "review", "ID (pass|fail|true|false|yes|no|1|0)",
1308 "note the results of running a test on a revision")
1309{
1310 if (args.size() != 2)
1311 throw usage(name);
1312
1313 revision_id r;
1314 complete(app, idx(args, 0)(), r);
1315 packet_db_writer dbw(app);
1316 cert_revision_testresult(r, idx(args, 1)(), app, dbw);
1317}
1318
1319CMD(approve, "review", "REVISION",
1320 "approve of a particular revision")
1321{
1322 if (args.size() != 1)
1323 throw usage(name);
1324
1325 revision_id r;
1326 complete(app, idx(args, 0)(), r);
1327 packet_db_writer dbw(app);
1328 cert_value branchname;
1329 guess_branch (r, app, branchname);
1330 app.set_branch(branchname());
1331 N(app.branch_name() != "", F("need --branch argument for approval"));
1332 cert_revision_in_branch(r, app.branch_name(), app, dbw);
1333}
1334
1335
1336CMD(disapprove, "review", "REVISION",
1337 "disapprove of a particular revision")
1338{
1339 if (args.size() != 1)
1340 throw usage(name);
1341
1342 revision_id r;
1343 revision_set rev, rev_inverse;
1344 boost::shared_ptr<change_set> cs_inverse(new change_set());
1345 complete(app, idx(args, 0)(), r);
1346 app.db.get_revision(r, rev);
1347
1348 N(rev.edges.size() == 1,
1349 F("revision %s has %d changesets, cannot invert\n") % r % rev.edges.size());
1350
1351 cert_value branchname;
1352 guess_branch (r, app, branchname);
1353 app.set_branch(branchname());
1354 N(app.branch_name() != "", F("need --branch argument for disapproval"));
1355
1356 edge_entry const & old_edge (*rev.edges.begin());
1357 rev_inverse.new_manifest = edge_old_manifest(old_edge);
1358 manifest_map m_old;
1359 app.db.get_manifest(edge_old_manifest(old_edge), m_old);
1360 invert_change_set(edge_changes(old_edge), m_old, *cs_inverse);
1361 rev_inverse.edges.insert(make_pair(r, make_pair(rev.new_manifest, cs_inverse)));
1362
1363 {
1364 transaction_guard guard(app.db);
1365 packet_db_writer dbw(app);
1366
1367 revision_id inv_id;
1368 revision_data rdat;
1369
1370 write_revision_set(rev_inverse, rdat);
1371 calculate_ident(rdat, inv_id);
1372 dbw.consume_revision_data(inv_id, rdat);
1373
1374 cert_revision_in_branch(inv_id, branchname, app, dbw);
1375 cert_revision_date_now(inv_id, app, dbw);
1376 cert_revision_author_default(inv_id, app, dbw);
1377 cert_revision_changelog(inv_id, (F("disapproval of revision %s") % r).str(), app, dbw);
1378 guard.commit();
1379 }
1380}
1381
1382CMD(comment, "review", "REVISION [COMMENT]",
1383 "comment on a particular revision")
1384{
1385 if (args.size() != 1 && args.size() != 2)
1386 throw usage(name);
1387
1388 string comment;
1389 if (args.size() == 2)
1390 comment = idx(args, 1)();
1391 else
1392 N(app.lua.hook_edit_comment("", "", comment),
1393 F("edit comment failed"));
1394
1395 N(comment.find_first_not_of(" \r\t\n") != string::npos,
1396 F("empty comment"));
1397
1398 revision_id r;
1399 complete(app, idx(args, 0)(), r);
1400 packet_db_writer dbw(app);
1401 cert_revision_comment(r, comment, app, dbw);
1402}
1403
1404
1405
1406CMD(add, "working copy", "PATH...", "add files to working copy")
1407{
1408 if (args.size() < 1)
1409 throw usage(name);
1410
1411 app.require_working_copy();
1412
1413 manifest_map m_old;
1414 calculate_base_manifest(app, m_old);
1415
1416 change_set::path_rearrangement work;
1417 get_path_rearrangement(work);
1418
1419 vector<file_path> paths;
1420 for (vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
1421 paths.push_back(app.prefix(*i));
1422
1423 build_additions(paths, m_old, app, work);
1424
1425 put_path_rearrangement(work);
1426
1427 update_any_attrs(app);
1428}
1429
1430CMD(drop, "working copy", "PATH...", "drop files from working copy")
1431{
1432 if (args.size() < 1)
1433 throw usage(name);
1434
1435 app.require_working_copy();
1436
1437 manifest_map m_old;
1438 calculate_base_manifest(app, m_old);
1439
1440 change_set::path_rearrangement work;
1441 get_path_rearrangement(work);
1442
1443 vector<file_path> paths;
1444 for (vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
1445 paths.push_back(app.prefix(*i));
1446
1447 build_deletions(paths, m_old, app, work);
1448
1449 put_path_rearrangement(work);
1450
1451 update_any_attrs(app);
1452}
1453
1454
1455CMD(rename, "working copy", "SRC DST", "rename entries in the working copy")
1456{
1457 if (args.size() != 2)
1458 throw usage(name);
1459
1460 app.require_working_copy();
1461
1462 manifest_map m_old;
1463 calculate_base_manifest(app, m_old);
1464
1465 change_set::path_rearrangement work;
1466 get_path_rearrangement(work);
1467
1468 build_rename(app.prefix(idx(args, 0)()), app.prefix(idx(args, 1)()), m_old, work);
1469
1470 put_path_rearrangement(work);
1471
1472 update_any_attrs(app);
1473}
1474
1475
1476// fload and fmerge are simple commands for debugging the line
1477// merger. fcommit is a helper for making single-file commits to monotone
1478// (such as automated processes might want to do).
1479
1480CMD(fcommit, "tree", "REVISION FILENAME [LOG_MESSAGE]",
1481 "commit change to a single file")
1482{
1483 if (args.size() != 2 && args.size() != 3)
1484 throw usage(name);
1485
1486 file_id old_fid, new_fid;
1487 revision_id old_rid, new_rid;
1488 manifest_id old_mid, new_mid;
1489 manifest_map old_man, new_man;
1490 file_data old_fdata, new_fdata;
1491 cert_value branchname;
1492 revision_data rdata;
1493 revision_set rev;
1494 boost::shared_ptr<change_set> cs(new change_set());
1495
1496 string log_message("");
1497 base64< gzip< data > > gz_dat;
1498 base64< gzip< delta > > gz_del;
1499 file_path pth(idx(args, 1)());
1500
1501 transaction_guard guard(app.db);
1502 packet_db_writer dbw(app);
1503
1504 complete(app, idx(args, 0)(), old_rid);
1505
1506 // find the old rev, manifest and file
1507 app.db.get_revision_manifest(old_rid, old_mid);
1508 app.db.get_manifest(old_mid, old_man);
1509 manifest_map::const_iterator i = old_man.find(pth);
1510 N(i != old_man.end(),
1511 F("cannot find file %s revision %s")
1512 % pth % old_rid);
1513
1514 // fetch the new file input
1515 string s = get_stdin();
1516 pack(data(s), gz_dat);
1517 new_fdata = file_data(gz_dat);
1518 calculate_ident(new_fdata, new_fid);
1519
1520 // diff and store the file edge
1521 old_fid = manifest_entry_id(i);
1522 app.db.get_file_version(old_fid, old_fdata);
1523 diff(old_fdata.inner(), new_fdata.inner(), gz_del);
1524 dbw.consume_file_delta(old_fid, new_fid,
1525 file_delta(gz_del));
1526
1527 // diff and store the manifest edge
1528 new_man = old_man;
1529 new_man[pth] = new_fid;
1530 calculate_ident(new_man, new_mid);
1531 diff(old_man, new_man, gz_del);
1532 dbw.consume_manifest_delta(old_mid, new_mid,
1533 manifest_delta(gz_del));
1534
1535 // build and store a changeset and revision
1536 cs->apply_delta(pth, old_fid, new_fid);
1537 rev.new_manifest = new_mid;
1538 rev.edges.insert(std::make_pair(old_rid,
1539 std::make_pair(old_mid, cs)));
1540 calculate_ident(rev, new_rid);
1541 write_revision_set(rev, rdata);
1542 dbw.consume_revision_data(new_rid, rdata);
1543
1544 // take care of any extra certs
1545 guess_branch (old_rid, app, branchname);
1546 app.set_branch(branchname());
1547
1548 if (args.size() == 3)
1549 log_message = idx(args, 2)();
1550 else
1551 get_log_message(rev, app, log_message);
1552
1553 N(log_message.find_first_not_of(" \r\t\n") != string::npos,
1554 F("empty log message"));
1555
1556 cert_revision_in_branch(new_rid, branchname, app, dbw);
1557 cert_revision_date_now(new_rid, app, dbw);
1558 cert_revision_author_default(new_rid, app, dbw);
1559 cert_revision_changelog(new_rid, log_message, app, dbw);
1560
1561 // finish off
1562 guard.commit();
1563}
1564
1565
1566CMD(fload, "debug", "", "load file contents into db")
1567{
1568 string s = get_stdin();
1569 base64< gzip< data > > gzd;
1570
1571 pack(data(s), gzd);
1572
1573 file_id f_id;
1574 file_data f_data(gzd);
1575
1576 calculate_ident (f_data, f_id);
1577
1578 packet_db_writer dbw(app);
1579 dbw.consume_file_data(f_id, f_data);
1580}
1581
1582CMD(fmerge, "debug", "<parent> <left> <right>", "merge 3 files and output result")
1583{
1584 if (args.size() != 3)
1585 throw usage(name);
1586
1587 file_id anc_id(idx(args, 0)()), left_id(idx(args, 1)()), right_id(idx(args, 2)());
1588 file_data anc, left, right;
1589 data anc_unpacked, left_unpacked, right_unpacked;
1590
1591 N(app.db.file_version_exists (anc_id),
1592 F("ancestor file id does not exist"));
1593
1594 N(app.db.file_version_exists (left_id),
1595 F("left file id does not exist"));
1596
1597 N(app.db.file_version_exists (right_id),
1598 F("right file id does not exist"));
1599
1600 app.db.get_file_version(anc_id, anc);
1601 app.db.get_file_version(left_id, left);
1602 app.db.get_file_version(right_id, right);
1603
1604 unpack(left.inner(), left_unpacked);
1605 unpack(anc.inner(), anc_unpacked);
1606 unpack(right.inner(), right_unpacked);
1607
1608 vector<string> anc_lines, left_lines, right_lines, merged_lines;
1609
1610 split_into_lines(anc_unpacked(), anc_lines);
1611 split_into_lines(left_unpacked(), left_lines);
1612 split_into_lines(right_unpacked(), right_lines);
1613 N(merge3(anc_lines, left_lines, right_lines, merged_lines), F("merge failed"));
1614 copy(merged_lines.begin(), merged_lines.end(), ostream_iterator<string>(cout, "\n"));
1615
1616}
1617
1618CMD(status, "informative", "[PATH]...", "show status of working copy")
1619{
1620 revision_set rs;
1621 manifest_map m_old, m_new;
1622 data tmp;
1623
1624 app.require_working_copy();
1625
1626 calculate_restricted_revision(app, args, rs, m_old, m_new);
1627
1628 write_revision_set(rs, tmp);
1629 cout << endl << tmp << endl;
1630}
1631
1632CMD(identify, "working copy", "[PATH]",
1633 "calculate identity of PATH or stdin")
1634{
1635 if (!(args.size() == 0 || args.size() == 1))
1636 throw usage(name);
1637
1638 data dat;
1639
1640 if (args.size() == 1)
1641 {
1642 read_localized_data(file_path(idx(args, 0)()), dat, app.lua);
1643 }
1644 else
1645 {
1646 dat = get_stdin();
1647 }
1648
1649 hexenc<id> ident;
1650 calculate_ident(dat, ident);
1651 cout << ident << endl;
1652}
1653
1654CMD(cat, "informative",
1655 "(file|manifest|revision) [ID]\n"
1656 "file REVISION FILENAME",
1657 "write file, manifest, or revision from database to stdout")
1658{
1659 if (args.size() < 1 || args.size() > 3)
1660 throw usage(name);
1661 if (args.size() == 3 && idx(args, 0)() != "file")
1662 throw usage(name);
1663
1664 transaction_guard guard(app.db);
1665
1666 if (idx(args, 0)() == "file")
1667 {
1668 file_id ident;
1669 if (args.size() == 1)
1670 throw usage(name);
1671 else if (args.size() == 2)
1672 {
1673 complete(app, idx(args, 1)(), ident);
1674
1675 N(app.db.file_version_exists(ident),
1676 F("no file version %s found in database") % ident);
1677 }
1678 else
1679 {
1680 revision_id rid;
1681 complete(app, idx(args, 1)(), rid);
1682 file_path fp(idx(args, 2)());
1683 manifest_id mid;
1684 app.db.get_revision_manifest(rid, mid);
1685 manifest_map m;
1686 app.db.get_manifest(mid, m);
1687 manifest_map::const_iterator i = m.find(fp);
1688 N(i != m.end(), F("no file '%s' found in revision '%s'\n") % fp % rid);
1689 ident = manifest_entry_id(i);
1690 }
1691
1692 file_data dat;
1693 L(F("dumping file %s\n") % ident);
1694 app.db.get_file_version(ident, dat);
1695 data unpacked;
1696 unpack(dat.inner(), unpacked);
1697 cout.write(unpacked().data(), unpacked().size());
1698 }
1699 else if (idx(args, 0)() == "manifest")
1700 {
1701 manifest_data dat;
1702 manifest_id ident;
1703
1704 if (args.size() == 1)
1705 {
1706 revision_set rev;
1707 manifest_map m_old, m_new;
1708
1709 app.require_working_copy();
1710 calculate_current_revision(app, rev, m_old, m_new);
1711
1712 calculate_ident(m_new, ident);
1713 write_manifest_map(m_new, dat);
1714 }
1715 else
1716 {
1717 complete(app, idx(args, 1)(), ident);
1718 N(app.db.manifest_version_exists(ident),
1719 F("no manifest version %s found in database") % ident);
1720 app.db.get_manifest_version(ident, dat);
1721 }
1722
1723 L(F("dumping manifest %s\n") % ident);
1724 data unpacked;
1725 unpack(dat.inner(), unpacked);
1726 cout.write(unpacked().data(), unpacked().size());
1727 }
1728
1729 else if (idx(args, 0)() == "revision")
1730 {
1731 revision_data dat;
1732 revision_id ident;
1733
1734 if (args.size() == 1)
1735 {
1736 revision_set rev;
1737 manifest_map m_old, m_new;
1738
1739 app.require_working_copy();
1740 calculate_current_revision(app, rev, m_old, m_new);
1741 calculate_ident(rev, ident);
1742 write_revision_set(rev, dat);
1743 }
1744 else
1745 {
1746 complete(app, idx(args, 1)(), ident);
1747 N(app.db.revision_exists(ident),
1748 F("no revision %s found in database") % ident);
1749 app.db.get_revision(ident, dat);
1750 }
1751
1752 L(F("dumping revision %s\n") % ident);
1753 data unpacked;
1754 unpack(dat.inner(), unpacked);
1755 cout.write(unpacked().data(), unpacked().size());
1756 }
1757 else
1758 throw usage(name);
1759
1760 guard.commit();
1761}
1762
1763
1764CMD(checkout, "tree", "REVISION DIRECTORY\nDIRECTORY\n",
1765 "check out revision from database into directory")
1766{
1767
1768 revision_id ident;
1769 string dir;
1770
1771 if (args.size() > 2)
1772 throw usage(name);
1773
1774 if (args.size() == 0 || args.size() == 1)
1775 {
1776 N(app.branch_name() != "", F("need --branch argument for branch-based checkout"));
1777
1778 // if no checkout dir specified, use branch name
1779 if (args.size() == 0)
1780 dir = app.branch_name();
1781 else
1782 dir = idx(args, 0)();
1783
1784 set<revision_id> heads;
1785 get_branch_heads(app.branch_name(), app, heads);
1786 N(heads.size() > 0, F("branch %s is empty") % app.branch_name);
1787 N(heads.size() == 1, F("branch %s has multiple heads") % app.branch_name);
1788 ident = *(heads.begin());
1789
1790 app.create_working_copy(dir);
1791 }
1792 else
1793 {
1794 dir = idx(args, 1)();
1795 complete(app, idx(args, 0)(), ident);
1796
1797 if (!app.branch_name().empty())
1798 {
1799 cert_value branch_name(app.branch_name());
1800 base64<cert_value> branch_encoded;
1801 encode_base64(branch_name, branch_encoded);
1802
1803 vector< revision<cert> > certs;
1804 app.db.get_revision_certs(ident, branch_cert_name, branch_encoded, certs);
1805
1806 L(F("found %d %s branch certs on revision %s\n")
1807 % certs.size()
1808 % app.branch_name
1809 % ident);
1810
1811 N(certs.size() != 0, F("revision %s is not a member of branch %s\n")
1812 % ident % app.branch_name);
1813 }
1814
1815 app.create_working_copy(dir);
1816 }
1817
1818 transaction_guard guard(app.db);
1819
1820 file_data data;
1821 manifest_id mid;
1822 manifest_map m;
1823
1824 N(app.db.revision_exists(ident),
1825 F("no revision %s found in database") % ident);
1826
1827 app.db.get_revision_manifest(ident, mid);
1828 put_revision_id(ident);
1829
1830 N(app.db.manifest_version_exists(mid),
1831 F("no manifest %s found in database") % ident);
1832
1833 L(F("checking out revision %s to directory %s\n") % ident % dir);
1834 app.db.get_manifest(mid, m);
1835
1836 for (manifest_map::const_iterator i = m.begin(); i != m.end(); ++i)
1837 {
1838 N(app.db.file_version_exists(manifest_entry_id(i)),
1839 F("no file %s found in database for %s")
1840 % manifest_entry_id(i) % manifest_entry_path(i));
1841
1842 file_data dat;
1843 L(F("writing file %s to %s\n")
1844 % manifest_entry_id(i) % manifest_entry_path(i));
1845 app.db.get_file_version(manifest_entry_id(i), dat);
1846 write_localized_data(manifest_entry_path(i), dat.inner(), app.lua);
1847 }
1848 remove_path_rearrangement();
1849 guard.commit();
1850 update_any_attrs(app);
1851}
1852
1853ALIAS(co, checkout, "tree", "REVISION DIRECTORY\nDIRECTORY",
1854 "check out revision from database; alias for checkout")
1855
1856CMD(heads, "tree", "", "show unmerged head revisions of branch")
1857{
1858 set<revision_id> heads;
1859 if (args.size() != 0)
1860 throw usage(name);
1861
1862 N(app.branch_name() != "",
1863 F("please specify a branch, with --branch=BRANCH"));
1864
1865 get_branch_heads(app.branch_name(), app, heads);
1866
1867 if (heads.size() == 0)
1868 P(F("branch '%s' is empty\n") % app.branch_name);
1869 else if (heads.size() == 1)
1870 P(F("branch '%s' is currently merged:\n") % app.branch_name);
1871 else
1872 P(F("branch '%s' is currently unmerged:\n") % app.branch_name);
1873
1874 for (set<revision_id>::const_iterator i = heads.begin();
1875 i != heads.end(); ++i)
1876 cout << describe_revision(app, *i) << endl;
1877}
1878
1879static void
1880ls_branches(string name, app_state & app, vector<utf8> const & args)
1881{
1882 vector< revision<cert> > certs;
1883 app.db.get_revision_certs(branch_cert_name, certs);
1884
1885 vector<string> names;
1886 for (size_t i = 0; i < certs.size(); ++i)
1887 {
1888 cert_value name;
1889 decode_base64(idx(certs, i).inner().value, name);
1890 if (!app.lua.hook_ignore_branch(name()))
1891 names.push_back(name());
1892 }
1893
1894 sort(names.begin(), names.end());
1895 names.erase(std::unique(names.begin(), names.end()), names.end());
1896 for (size_t i = 0; i < names.size(); ++i)
1897 cout << idx(names, i) << endl;
1898}
1899
1900static void
1901ls_epochs(string name, app_state & app, vector<utf8> const & args)
1902{
1903 std::map<cert_value, epoch_data> epochs;
1904 app.db.get_epochs(epochs);
1905
1906 if (args.size() == 0)
1907 {
1908 for (std::map<cert_value, epoch_data>::const_iterator i = epochs.begin();
1909 i != epochs.end(); ++i)
1910 {
1911 cout << i->second << " " << i->first << endl;
1912 }
1913 }
1914 else
1915 {
1916 for (vector<utf8>::const_iterator i = args.begin(); i != args.end();
1917 ++i)
1918 {
1919 std::map<cert_value, epoch_data>::const_iterator j = epochs.find(cert_value((*i)()));
1920 N(j != epochs.end(), F("no epoch for branch %s\n") % *i);
1921 cout << j->second << " " << j->first << endl;
1922 }
1923 }
1924}
1925
1926static void
1927ls_tags(string name, app_state & app, vector<utf8> const & args)
1928{
1929 vector< revision<cert> > certs;
1930 app.db.get_revision_certs(tag_cert_name, certs);
1931
1932 for (size_t i = 0; i < certs.size(); ++i)
1933 {
1934 cert_value name;
1935 decode_base64(idx(certs, i).inner().value, name);
1936 cout << name << " "
1937 << idx(certs,i).inner().ident << " "
1938 << idx(certs,i).inner().key << endl;
1939 }
1940}
1941
1942static void
1943ls_vars(string name, app_state & app, vector<utf8> const & args)
1944{
1945 bool filterp;
1946 var_domain filter;
1947 if (args.size() == 0)
1948 {
1949 filterp = false;
1950 }
1951 else if (args.size() == 1)
1952 {
1953 filterp = true;
1954 internalize_var_domain(idx(args, 0), filter);
1955 }
1956 else
1957 throw usage(name);
1958
1959 map<var_key, var_value> vars;
1960 app.db.get_vars(vars);
1961 for (std::map<var_key, var_value>::const_iterator i = vars.begin();
1962 i != vars.end(); ++i)
1963 {
1964 if (filterp && !(i->first.first == filter))
1965 continue;
1966 external ext_domain, ext_name;
1967 externalize_var_domain(i->first.first, ext_domain);
1968 cout << ext_domain << ": " << i->first.second << " " << i->second << endl;
1969 }
1970}
1971
1972struct unknown_itemizer : public tree_walker
1973{
1974 app_state & app;
1975 manifest_map & man;
1976 bool want_ignored;
1977 unknown_itemizer(app_state & a, manifest_map & m, bool i)
1978 : app(a), man(m), want_ignored(i) {}
1979 virtual void visit_file(file_path const & path)
1980 {
1981 if (app.restriction_includes(path) && man.find(path) == man.end())
1982 {
1983 if (want_ignored)
1984 {
1985 if (app.lua.hook_ignore_file(path))
1986 cout << path() << endl;
1987 }
1988 else
1989 {
1990 if (!app.lua.hook_ignore_file(path))
1991 cout << path() << endl;
1992 }
1993 }
1994 }
1995};
1996
1997
1998static void
1999ls_unknown (app_state & app, bool want_ignored, vector<utf8> const & args)
2000{
2001 app.require_working_copy();
2002
2003 revision_set rev;
2004 manifest_map m_old, m_new;
2005 calculate_restricted_revision(app, args, rev, m_old, m_new);
2006 unknown_itemizer u(app, m_new, want_ignored);
2007 walk_tree(u);
2008}
2009
2010static void
2011ls_missing (app_state & app, vector<utf8> const & args)
2012{
2013 revision_set rev;
2014 revision_id rid;
2015 manifest_id mid;
2016 manifest_map man;
2017 change_set::path_rearrangement work, included, excluded;
2018 path_set old_paths, new_paths;
2019
2020 app.require_working_copy();
2021
2022 calculate_base_revision(app, rid, mid, man);
2023
2024 get_path_rearrangement(work);
2025 extract_path_set(man, old_paths);
2026
2027 path_set valid_paths(old_paths);
2028 extract_rearranged_paths(work, valid_paths);
2029 add_intermediate_paths(valid_paths);
2030
2031 app.set_restriction(valid_paths, args);
2032
2033 restrict_path_rearrangement(work, included, excluded, app);
2034
2035 apply_path_rearrangement(old_paths, included, new_paths);
2036
2037 for (path_set::const_iterator i = new_paths.begin(); i != new_paths.end(); ++i)
2038 {
2039 if (app.restriction_includes(*i) && !file_exists(*i))
2040 cout << *i << endl;
2041 }
2042}
2043
2044
2045CMD(list, "informative",
2046 "certs ID\n"
2047 "keys [PATTERN]\n"
2048 "branches\n"
2049 "epochs [BRANCH [...]]\n"
2050 "tags\n"
2051 "vars [DOMAIN]\n"
2052 "unknown\n"
2053 "ignored\n"
2054 "missing",
2055 "show database objects, or unknown, intentionally ignored, or missing state files")
2056{
2057 if (args.size() == 0)
2058 throw usage(name);
2059
2060 vector<utf8>::const_iterator i = args.begin();
2061 ++i;
2062 vector<utf8> removed (i, args.end());
2063 if (idx(args, 0)() == "certs")
2064 ls_certs(name, app, removed);
2065 else if (idx(args, 0)() == "keys")
2066 ls_keys(name, app, removed);
2067 else if (idx(args, 0)() == "branches")
2068 ls_branches(name, app, removed);
2069 else if (idx(args, 0)() == "epochs")
2070 ls_epochs(name, app, removed);
2071 else if (idx(args, 0)() == "tags")
2072 ls_tags(name, app, removed);
2073 else if (idx(args, 0)() == "vars")
2074 ls_vars(name, app, removed);
2075 else if (idx(args, 0)() == "unknown")
2076 ls_unknown(app, false, removed);
2077 else if (idx(args, 0)() == "ignored")
2078 ls_unknown(app, true, removed);
2079 else if (idx(args, 0)() == "missing")
2080 ls_missing(app, removed);
2081 else
2082 throw usage(name);
2083}
2084
2085ALIAS(ls, list, "informative",
2086 "certs ID\n"
2087 "keys [PATTERN]\n"
2088 "branches\n"
2089 "epochs [BRANCH [...]]\n"
2090 "tags\n"
2091 "vars [DOMAIN]\n"
2092 "unknown\n"
2093 "ignored\n"
2094 "missing",
2095 "show database objects, or unknown, intentionally ignored, or missing state files; alias for list")
2096
2097
2098CMD(mdelta, "packet i/o", "OLDID NEWID", "write manifest delta packet to stdout")
2099{
2100 if (args.size() != 2)
2101 throw usage(name);
2102
2103 packet_writer pw(cout);
2104
2105 manifest_id m_old_id, m_new_id;
2106 manifest_map m_old, m_new;
2107
2108 complete(app, idx(args, 0)(), m_old_id);
2109 complete(app, idx(args, 1)(), m_new_id);
2110
2111 app.db.get_manifest(m_old_id, m_old);
2112 app.db.get_manifest(m_new_id, m_new);
2113
2114 base64< gzip<delta> > del;
2115 diff(m_old, m_new, del);
2116 pw.consume_manifest_delta(m_old_id, m_new_id,
2117 manifest_delta(del));
2118}
2119
2120CMD(fdelta, "packet i/o", "OLDID NEWID", "write file delta packet to stdout")
2121{
2122 if (args.size() != 2)
2123 throw usage(name);
2124
2125 packet_writer pw(cout);
2126
2127 file_id f_old_id, f_new_id;
2128 file_data f_old_data, f_new_data;
2129
2130 complete(app, idx(args, 0)(), f_old_id);
2131 complete(app, idx(args, 1)(), f_new_id);
2132
2133 app.db.get_file_version(f_old_id, f_old_data);
2134 app.db.get_file_version(f_new_id, f_new_data);
2135 base64< gzip<delta> > del;
2136 diff(f_old_data.inner(), f_new_data.inner(), del);
2137 pw.consume_file_delta(f_old_id, f_new_id, file_delta(del));
2138}
2139
2140CMD(rdata, "packet i/o", "ID", "write revision data packet to stdout")
2141{
2142 if (args.size() != 1)
2143 throw usage(name);
2144
2145 packet_writer pw(cout);
2146
2147 revision_id r_id;
2148 revision_data r_data;
2149
2150 complete(app, idx(args, 0)(), r_id);
2151
2152 app.db.get_revision(r_id, r_data);
2153 pw.consume_revision_data(r_id, r_data);
2154}
2155
2156CMD(mdata, "packet i/o", "ID", "write manifest data packet to stdout")
2157{
2158 if (args.size() != 1)
2159 throw usage(name);
2160
2161 packet_writer pw(cout);
2162
2163 manifest_id m_id;
2164 manifest_data m_data;
2165
2166 complete(app, idx(args, 0)(), m_id);
2167
2168 app.db.get_manifest_version(m_id, m_data);
2169 pw.consume_manifest_data(m_id, m_data);
2170}
2171
2172
2173CMD(fdata, "packet i/o", "ID", "write file data packet to stdout")
2174{
2175 if (args.size() != 1)
2176 throw usage(name);
2177
2178 packet_writer pw(cout);
2179
2180 file_id f_id;
2181 file_data f_data;
2182
2183 complete(app, idx(args, 0)(), f_id);
2184
2185 app.db.get_file_version(f_id, f_data);
2186 pw.consume_file_data(f_id, f_data);
2187}
2188
2189
2190CMD(certs, "packet i/o", "ID", "write cert packets to stdout")
2191{
2192 if (args.size() != 1)
2193 throw usage(name);
2194
2195 packet_writer pw(cout);
2196
2197 revision_id r_id;
2198 vector< revision<cert> > certs;
2199
2200 complete(app, idx(args, 0)(), r_id);
2201
2202 app.db.get_revision_certs(r_id, certs);
2203 for (size_t i = 0; i < certs.size(); ++i)
2204 pw.consume_revision_cert(idx(certs, i));
2205}
2206
2207CMD(pubkey, "packet i/o", "ID", "write public key packet to stdout")
2208{
2209 if (args.size() != 1)
2210 throw usage(name);
2211
2212 rsa_keypair_id ident(idx(args, 0)());
2213 N(app.db.public_key_exists(ident),
2214 F("public key '%s' does not exist in database") % idx(args, 0)());
2215
2216 packet_writer pw(cout);
2217 base64< rsa_pub_key > key;
2218 app.db.get_key(ident, key);
2219 pw.consume_public_key(ident, key);
2220}
2221
2222CMD(privkey, "packet i/o", "ID", "write private key packet to stdout")
2223{
2224 if (args.size() != 1)
2225 throw usage(name);
2226
2227 rsa_keypair_id ident(idx(args, 0)());
2228 N(app.db.private_key_exists(ident),
2229 F("private key '%s' does not exist in database") % idx(args, 0)());
2230
2231 packet_writer pw(cout);
2232 base64< arc4<rsa_priv_key> > key;
2233 app.db.get_key(ident, key);
2234 pw.consume_private_key(ident, key);
2235}
2236
2237
2238CMD(read, "packet i/o", "", "read packets from stdin")
2239{
2240 packet_db_writer dbw(app, true);
2241 size_t count = read_packets(cin, dbw);
2242 N(count != 0, F("no packets found on stdin"));
2243 if (count == 1)
2244 P(F("read 1 packet\n"));
2245 else
2246 P(F("read %d packets\n") % count);
2247}
2248
2249
2250CMD(reindex, "network", "",
2251 "rebuild the indices used to sync over the network")
2252{
2253 if (args.size() > 0)
2254 throw usage(name);
2255
2256 transaction_guard guard(app.db);
2257 ui.set_tick_trailer("rehashing db");
2258 app.db.rehash();
2259 guard.commit();
2260}
2261
2262static const var_key default_server_key(var_domain("database"),
2263 var_name("default-server"));
2264static const var_key default_collection_key(var_domain("database"),
2265 var_name("default-collection"));
2266
2267static void
2268process_netsync_client_args(std::string const & name,
2269 std::vector<utf8> const & args,
2270 utf8 & addr, std::vector<utf8> & collections,
2271 app_state & app)
2272{
2273 if (args.size() > 2)
2274 throw usage(name);
2275
2276 if (args.size() >= 1)
2277 {
2278 addr = idx(args, 0);
2279 if (!app.db.var_exists(default_server_key))
2280 {
2281 P(F("setting default server to %s\n") % addr);
2282 app.db.set_var(default_server_key, var_value(addr()));
2283 }
2284 }
2285 else
2286 {
2287 N(app.db.var_exists(default_server_key),
2288 F("no server given and no default server set"));
2289 var_value addr_value;
2290 app.db.get_var(default_server_key, addr_value);
2291 addr = utf8(addr_value());
2292 L(F("using default server address: %s\n") % addr);
2293 }
2294 // NB: even though the netsync code wants a vector of collections, in fact
2295 // this only works for the server; when we're a client, our vector must have
2296 // length exactly 1.
2297 utf8 collection;
2298 if (args.size() >= 2)
2299 {
2300 collection = idx(args, 1);
2301 if (!app.db.var_exists(default_collection_key))
2302 {
2303 P(F("setting default collection to %s\n") % collection);
2304 app.db.set_var(default_collection_key, var_value(collection()));
2305 }
2306 }
2307 else
2308 {
2309 N(app.db.var_exists(default_collection_key),
2310 F("no collection given and no default collection set"));
2311 var_value collection_value;
2312 app.db.get_var(default_collection_key, collection_value);
2313 collection = utf8(collection_value());
2314 L(F("using default collection: %s\n") % collection);
2315 }
2316 collections.push_back(collection);
2317}
2318
2319CMD(push, "network", "[ADDRESS[:PORTNUMBER] [COLLECTION]]",
2320 "push COLLECTION to netsync server at ADDRESS")
2321{
2322 utf8 addr;
2323 vector<utf8> collections;
2324 process_netsync_client_args(name, args, addr, collections, app);
2325
2326 rsa_keypair_id key;
2327 N(guess_default_key(key, app), F("could not guess default signing key"));
2328 app.signing_key = key;
2329
2330 run_netsync_protocol(client_voice, source_role, addr, collections, app);
2331}
2332
2333CMD(pull, "network", "[ADDRESS[:PORTNUMBER] [COLLECTION]]",
2334 "pull COLLECTION from netsync server at ADDRESS")
2335{
2336 utf8 addr;
2337 vector<utf8> collections;
2338 process_netsync_client_args(name, args, addr, collections, app);
2339
2340 if (app.signing_key() == "")
2341 W(F("doing anonymous pull\n"));
2342
2343 run_netsync_protocol(client_voice, sink_role, addr, collections, app);
2344}
2345
2346CMD(sync, "network", "[ADDRESS[:PORTNUMBER] [COLLECTION]]",
2347 "sync COLLECTION with netsync server at ADDRESS")
2348{
2349 utf8 addr;
2350 vector<utf8> collections;
2351 process_netsync_client_args(name, args, addr, collections, app);
2352
2353 rsa_keypair_id key;
2354 N(guess_default_key(key, app), F("could not guess default signing key"));
2355 app.signing_key = key;
2356
2357 run_netsync_protocol(client_voice, source_and_sink_role, addr, collections, app);
2358}
2359
2360CMD(serve, "network", "ADDRESS[:PORTNUMBER] COLLECTION...",
2361 "listen on ADDRESS and serve COLLECTION to connecting clients")
2362{
2363 if (args.size() < 2)
2364 throw usage(name);
2365
2366 rsa_keypair_id key;
2367 N(guess_default_key(key, app), F("could not guess default signing key"));
2368 app.signing_key = key;
2369
2370 N(app.lua.hook_persist_phrase_ok(),
2371 F("need permission to store persistent passphrase (see hook persist_phrase_ok())"));
2372 require_password(key, app);
2373
2374 utf8 addr(idx(args,0));
2375 vector<utf8> collections(args.begin() + 1, args.end());
2376 run_netsync_protocol(server_voice, source_and_sink_role, addr, collections, app);
2377}
2378
2379
2380CMD(db, "database",
2381 "init\n"
2382 "info\n"
2383 "version\n"
2384 "dump\n"
2385 "load\n"
2386 "migrate\n"
2387 "execute\n"
2388 "check\n"
2389 "changesetify\n"
2390 "rebuild\n"
2391 "set_epoch BRANCH EPOCH\n",
2392 "manipulate database state")
2393{
2394 if (args.size() == 1)
2395 {
2396 if (idx(args, 0)() == "init")
2397 app.db.initialize();
2398 else if (idx(args, 0)() == "info")
2399 app.db.info(cout);
2400 else if (idx(args, 0)() == "version")
2401 app.db.version(cout);
2402 else if (idx(args, 0)() == "dump")
2403 app.db.dump(cout);
2404 else if (idx(args, 0)() == "load")
2405 app.db.load(cin);
2406 else if (idx(args, 0)() == "migrate")
2407 app.db.migrate();
2408 else if (idx(args, 0)() == "check")
2409 check_db(app);
2410 else if (idx(args, 0)() == "changesetify")
2411 build_changesets_from_manifest_ancestry(app);
2412 else if (idx(args, 0)() == "rebuild")
2413 build_changesets_from_existing_revs(app);
2414 else
2415 throw usage(name);
2416 }
2417 else if (args.size() == 2)
2418 {
2419 if (idx(args, 0)() == "execute")
2420 app.db.debug(idx(args, 1)(), cout);
2421 else if (idx(args, 0)() == "clear_epoch")
2422 app.db.clear_epoch(cert_value(idx(args, 1)()));
2423 else
2424 throw usage(name);
2425 }
2426 else if (args.size() == 3)
2427 {
2428 if (idx(args, 0)() == "set_epoch")
2429 app.db.set_epoch(cert_value(idx(args, 1)()),
2430 epoch_data(idx(args,2)()));
2431 else
2432 throw usage(name);
2433 }
2434 else
2435 throw usage(name);
2436}
2437
2438CMD(attr, "working copy", "set FILE ATTR VALUE\nget FILE [ATTR]",
2439 "get or set file attributes")
2440{
2441 if (args.size() < 2 || args.size() > 4)
2442 throw usage(name);
2443
2444 app.require_working_copy();
2445
2446 data attr_data;
2447 file_path attr_path;
2448 attr_map attrs;
2449 get_attr_path(attr_path);
2450
2451 if (file_exists(attr_path))
2452 {
2453 read_data(attr_path, attr_data);
2454 read_attr_map(attr_data, attrs);
2455 }
2456
2457 file_path path = app.prefix(idx(args,1)());
2458 N(file_exists(path), F("file '%s' not found") % path);
2459
2460 if (idx(args, 0)() == "set")
2461 {
2462 if (args.size() != 4)
2463 throw usage(name);
2464 attrs[path][idx(args, 2)()] = idx(args, 3)();
2465 write_attr_map(attr_data, attrs);
2466 write_data(attr_path, attr_data);
2467
2468 {
2469 // check to make sure .mt-attr exists in
2470 // current manifest.
2471 manifest_map man;
2472 calculate_base_manifest(app, man);
2473 if (man.find(attr_path) == man.end())
2474 {
2475 P(F("registering %s file in working copy\n") % attr_path);
2476 change_set::path_rearrangement work;
2477 get_path_rearrangement(work);
2478 vector<file_path> paths;
2479 paths.push_back(attr_path);
2480 build_additions(paths, man, app, work);
2481 put_path_rearrangement(work);
2482 }
2483 }
2484
2485 }
2486 else if (idx(args, 0)() == "get")
2487 {
2488 if (args.size() != 2 && args.size() != 3)
2489 throw usage(name);
2490
2491 attr_map::const_iterator i = attrs.find(path);
2492 if (i == attrs.end())
2493 cout << "no attributes for " << path << endl;
2494 else
2495 {
2496 if (args.size() == 2)
2497 {
2498 for (std::map<std::string, std::string>::const_iterator j = i->second.begin();
2499 j != i->second.end(); ++j)
2500 cout << path << " : " << j->first << "=" << j->second << endl;
2501 }
2502 else
2503 {
2504 std::map<std::string, std::string>::const_iterator j = i->second.find(idx(args, 2)());
2505 if (j == i->second.end())
2506 cout << "no attribute " << idx(args, 2)() << " on file " << path << endl;
2507 else
2508 cout << path << " : " << j->first << "=" << j->second << endl;
2509 }
2510 }
2511 }
2512 else
2513 throw usage(name);
2514}
2515CMD(commit, "working copy", "[--message=STRING] [PATH]...",
2516 "commit working copy to database")
2517{
2518 string log_message("");
2519 revision_set rs;
2520 revision_id rid;
2521 manifest_map m_old, m_new;
2522
2523 app.require_working_copy();
2524
2525 // preserve excluded work for future commmits
2526 change_set::path_rearrangement excluded_work;
2527 calculate_restricted_revision(app, args, rs, m_old, m_new, excluded_work);
2528 calculate_ident(rs, rid);
2529
2530 N(!(rs.edges.size() == 0 ||
2531 edge_changes(rs.edges.begin()).empty()),
2532 F("no changes to commit\n"));
2533
2534 cert_value branchname;
2535 I(rs.edges.size() == 1);
2536
2537 guess_branch (edge_old_revision(rs.edges.begin()), app, branchname);
2538 app.set_branch(branchname());
2539
2540 P(F("beginning commit on branch '%s'\n") % branchname);
2541 L(F("new manifest %s\n") % rs.new_manifest);
2542 L(F("new revision %s\n") % rid);
2543
2544 // get log message
2545 N(!(app.message().length() > 0 && has_contents_user_log()),
2546 F("MT/log is non-empty and --message supplied\n"
2547 "perhaps move or delete MT/log,\n"
2548 "or remove --message from the command line?"));
2549
2550 if (app.message().length() > 0)
2551 log_message = app.message();
2552 else
2553 get_log_message(rs, app, log_message);
2554
2555 N(log_message.find_first_not_of(" \r\t\n") != string::npos,
2556 F("empty log message"));
2557
2558 transaction_guard guard(app.db);
2559 {
2560 packet_db_writer dbw(app);
2561
2562 if (app.db.revision_exists(rid))
2563 {
2564 W(F("revision %s already in database\n") % rid);
2565 }
2566 else
2567 {
2568 // new revision
2569 L(F("inserting new revision %s\n") % rid);
2570
2571 I(rs.edges.size() == 1);
2572 edge_map::const_iterator edge = rs.edges.begin();
2573 I(edge != rs.edges.end());
2574
2575 // process manifest delta or new manifest
2576 if (app.db.manifest_version_exists(rs.new_manifest))
2577 {
2578 L(F("skipping manifest %s, already in database\n") % rs.new_manifest);
2579 }
2580 else if (app.db.manifest_version_exists(edge_old_manifest(edge)))
2581 {
2582 L(F("inserting manifest delta %s -> %s\n")
2583 % edge_old_manifest(edge)
2584 % rs.new_manifest);
2585 base64< gzip<delta> > del;
2586 diff(m_old, m_new, del);
2587 dbw.consume_manifest_delta(edge_old_manifest(edge),
2588 rs.new_manifest,
2589 manifest_delta(del));
2590 }
2591 else
2592 {
2593 L(F("inserting full manifest %s\n") % rs.new_manifest);
2594 manifest_data m_new_data;
2595 write_manifest_map(m_new, m_new_data);
2596 dbw.consume_manifest_data(rs.new_manifest, m_new_data);
2597 }
2598
2599 // process file deltas or new files
2600 for (change_set::delta_map::const_iterator i = edge_changes(edge).deltas.begin();
2601 i != edge_changes(edge).deltas.end(); ++i)
2602 {
2603 if (! delta_entry_src(i).inner()().empty() &&
2604 app.db.file_version_exists(delta_entry_dst(i)))
2605 {
2606 L(F("skipping file delta %s, already in database\n")
2607 % delta_entry_dst(i));
2608 }
2609 else if (! delta_entry_src(i).inner()().empty() &&
2610 app.db.file_version_exists(delta_entry_src(i)))
2611 {
2612 L(F("inserting delta %s -> %s\n")
2613 % delta_entry_src(i) % delta_entry_dst(i));
2614 file_data old_data;
2615 base64< gzip<data> > new_data;
2616 app.db.get_file_version(delta_entry_src(i), old_data);
2617 read_localized_data(delta_entry_path(i), new_data, app.lua);
2618 // sanity check
2619 hexenc<id> tid;
2620 calculate_ident(new_data, tid);
2621 I(tid == delta_entry_dst(i).inner());
2622 base64< gzip<delta> > del;
2623 diff(old_data.inner(), new_data, del);
2624 dbw.consume_file_delta(delta_entry_src(i),
2625 delta_entry_dst(i),
2626 file_delta(del));
2627 }
2628 else
2629 {
2630 L(F("inserting full version %s\n") % delta_entry_dst(i));
2631 base64< gzip<data> > new_data;
2632 read_localized_data(delta_entry_path(i), new_data, app.lua);
2633 // sanity check
2634 hexenc<id> tid;
2635 calculate_ident(new_data, tid);
2636 I(tid == delta_entry_dst(i).inner());
2637 dbw.consume_file_data(delta_entry_dst(i), file_data(new_data));
2638 }
2639 }
2640 }
2641
2642 revision_data rdat;
2643 write_revision_set(rs, rdat);
2644 dbw.consume_revision_data(rid, rdat);
2645
2646 cert_revision_in_branch(rid, branchname, app, dbw);
2647 cert_revision_date_now(rid, app, dbw);
2648 cert_revision_author_default(rid, app, dbw);
2649 cert_revision_changelog(rid, log_message, app, dbw);
2650 }
2651
2652 guard.commit();
2653
2654 // small race condition here...
2655 put_path_rearrangement(excluded_work);
2656 put_revision_id(rid);
2657 P(F("committed revision %s\n") % rid);
2658
2659 blank_user_log();
2660
2661 update_any_attrs(app);
2662
2663 {
2664 // tell lua what happened. yes, we might lose some information here,
2665 // but it's just an indicator for lua, eg. to post stuff to a mailing
2666 // list. if the user *really* cares about cert validity, multiple certs
2667 // with same name, etc. they can inquire further, later.
2668 map<cert_name, cert_value> certs;
2669 vector< revision<cert> > ctmp;
2670 app.db.get_revision_certs(rid, ctmp);
2671 for (vector< revision<cert> >::const_iterator i = ctmp.begin();
2672 i != ctmp.end(); ++i)
2673 {
2674 cert_value vtmp;
2675 decode_base64(i->inner().value, vtmp);
2676 certs.insert(make_pair(i->inner().name, vtmp));
2677 }
2678 app.lua.hook_note_commit(rid, certs);
2679 }
2680}
2681
2682
2683static void
2684dump_diffs(change_set::delta_map const & deltas,
2685 app_state & app,
2686 bool new_is_archived,
2687 diff_type type)
2688{
2689
2690 for (change_set::delta_map::const_iterator i = deltas.begin();
2691 i != deltas.end(); ++i)
2692 {
2693 if (null_id(delta_entry_src(i)))
2694 {
2695 data unpacked;
2696 vector<string> lines;
2697
2698 if (new_is_archived)
2699 {
2700 file_data dat;
2701 app.db.get_file_version(delta_entry_dst(i), dat);
2702 unpack(dat.inner(), unpacked);
2703 }
2704 else
2705 {
2706 read_localized_data(delta_entry_path(i),
2707 unpacked, app.lua);
2708 }
2709
2710 if (guess_binary(unpacked()))
2711 cout << "# " << delta_entry_path(i) << " is binary\n";
2712 else
2713 {
2714 split_into_lines(unpacked(), lines);
2715 if (! lines.empty())
2716 {
2717 cout << (F("--- %s\n") % delta_entry_path(i))
2718 << (F("+++ %s\n") % delta_entry_path(i))
2719 << (F("@@ -0,0 +1,%d @@\n") % lines.size());
2720 for (vector<string>::const_iterator j = lines.begin();
2721 j != lines.end(); ++j)
2722 {
2723 cout << "+" << *j << endl;
2724 }
2725 }
2726 }
2727 }
2728 else
2729 {
2730 file_data f_old;
2731 gzip<data> decoded_old;
2732 data decompressed_old, decompressed_new;
2733 vector<string> old_lines, new_lines;
2734
2735 app.db.get_file_version(delta_entry_src(i), f_old);
2736 decode_base64(f_old.inner(), decoded_old);
2737 decode_gzip(decoded_old, decompressed_old);
2738
2739 if (new_is_archived)
2740 {
2741 file_data f_new;
2742 gzip<data> decoded_new;
2743 app.db.get_file_version(delta_entry_dst(i), f_new);
2744 decode_base64(f_new.inner(), decoded_new);
2745 decode_gzip(decoded_new, decompressed_new);
2746 }
2747 else
2748 {
2749 read_localized_data(delta_entry_path(i),
2750 decompressed_new, app.lua);
2751 }
2752
2753 if (guess_binary(decompressed_new()) ||
2754 guess_binary(decompressed_old()))
2755 cout << "# " << delta_entry_path(i) << " is binary\n";
2756 else
2757 {
2758 split_into_lines(decompressed_old(), old_lines);
2759 split_into_lines(decompressed_new(), new_lines);
2760 make_diff(delta_entry_path(i)(),
2761 delta_entry_path(i)(),
2762 old_lines, new_lines,
2763 cout, type);
2764 }
2765 }
2766 }
2767}
2768
2769void do_diff(const string & name,
2770 app_state & app,
2771 vector<utf8> const & args,
2772 diff_type type)
2773{
2774 revision_set r_old, r_new;
2775 manifest_map m_new;
2776 bool new_is_archived;
2777
2778 change_set composite;
2779
2780 // initialize before transaction so we have a database to work with
2781
2782 if (app.revision_selectors.size() == 0)
2783 app.require_working_copy();
2784 else if (app.revision_selectors.size() == 1)
2785 app.require_working_copy();
2786
2787 transaction_guard guard(app.db);
2788
2789 if (app.revision_selectors.size() == 0)
2790 {
2791 manifest_map m_old;
2792 calculate_restricted_revision(app, args, r_new, m_old, m_new);
2793 I(r_new.edges.size() == 1 || r_new.edges.size() == 0);
2794 if (r_new.edges.size() == 1)
2795 composite = edge_changes(r_new.edges.begin());
2796 new_is_archived = false;
2797 }
2798 else if (app.revision_selectors.size() == 1)
2799 {
2800 revision_id r_old_id;
2801 manifest_map m_old;
2802 complete(app, idx(app.revision_selectors, 0)(), r_old_id);
2803 N(app.db.revision_exists(r_old_id),
2804 F("revision %s does not exist") % r_old_id);
2805 app.db.get_revision(r_old_id, r_old);
2806 calculate_current_revision(app, r_new, m_old, m_new);
2807 I(r_new.edges.size() == 1 || r_new.edges.size() == 0);
2808 N(r_new.edges.size() == 1, F("current revision has no ancestor"));
2809 new_is_archived = false;
2810 }
2811 else if (app.revision_selectors.size() == 2)
2812 {
2813 revision_id r_old_id, r_new_id;
2814 manifest_id m_new_id;
2815
2816 complete(app, idx(app.revision_selectors, 0)(), r_old_id);
2817 complete(app, idx(app.revision_selectors, 1)(), r_new_id);
2818
2819 N(app.db.revision_exists(r_old_id),
2820 F("revision %s does not exist") % r_old_id);
2821 app.db.get_revision(r_old_id, r_old);
2822
2823 N(app.db.revision_exists(r_new_id),
2824 F("revision %s does not exist") % r_new_id);
2825 app.db.get_revision(r_new_id, r_new);
2826
2827 app.db.get_revision_manifest(r_new_id, m_new_id);
2828 app.db.get_manifest(m_new_id, m_new);
2829
2830 new_is_archived = true;
2831 }
2832 else
2833 {
2834 throw usage(name);
2835 }
2836
2837 if (app.revision_selectors.size() > 0)
2838 {
2839 revision_id new_id, src_id, dst_id, anc_id;
2840 calculate_ident(r_old, src_id);
2841 calculate_ident(r_new, new_id);
2842 if (new_is_archived)
2843 dst_id = new_id;
2844 else
2845 {
2846 I(r_new.edges.size() == 1);
2847 dst_id = edge_old_revision(r_new.edges.begin());
2848 }
2849
2850 N(find_least_common_ancestor(src_id, dst_id, anc_id, app),
2851 F("no common ancestor for %s and %s") % src_id % dst_id);
2852
2853 if (src_id == anc_id)
2854 {
2855 calculate_composite_change_set(src_id, dst_id, app, composite);
2856 L(F("calculated diff via direct analysis\n"));
2857 }
2858 else if (!(src_id == anc_id) && dst_id == anc_id)
2859 {
2860 change_set tmp;
2861 calculate_composite_change_set(dst_id, src_id, app, tmp);
2862 invert_change_set(tmp, m_new, composite);
2863 L(F("calculated diff via inverted direct analysis\n"));
2864 }
2865 else
2866 {
2867 change_set anc_to_src, src_to_anc, anc_to_dst;
2868 manifest_id anc_m_id;
2869 manifest_map m_anc;
2870
2871 I(!(src_id == anc_id || dst_id == anc_id));
2872
2873 app.db.get_revision_manifest(anc_id, anc_m_id);
2874 app.db.get_manifest(anc_m_id, m_anc);
2875
2876 calculate_composite_change_set(anc_id, src_id, app, anc_to_src);
2877 invert_change_set(anc_to_src, m_anc, src_to_anc);
2878 calculate_composite_change_set(anc_id, dst_id, app, anc_to_dst);
2879 concatenate_change_sets(src_to_anc, anc_to_dst, composite);
2880 L(F("calculated diff via common ancestor %s\n") % anc_id);
2881 }
2882
2883 if (!new_is_archived)
2884 {
2885 L(F("concatenating un-committed changeset to composite\n"));
2886 change_set tmp;
2887 I(r_new.edges.size() == 1);
2888 concatenate_change_sets(composite, edge_changes(r_new.edges.begin()), tmp);
2889 composite = tmp;
2890 }
2891
2892 change_set included, excluded;
2893 calculate_restricted_change_set(app, args, composite, included, excluded);
2894 composite = included;
2895
2896 }
2897
2898 data summary;
2899 write_change_set(composite, summary);
2900
2901 vector<string> lines;
2902 split_into_lines(summary(), lines);
2903 cout << "# " << endl;
2904 if (summary().size() > 0)
2905 {
2906 for (vector<string>::iterator i = lines.begin(); i != lines.end(); ++i)
2907 cout << "# " << *i << endl;
2908 }
2909 else
2910 {
2911 cout << F("# no changes") << endl;
2912 }
2913 cout << "# " << endl;
2914
2915 dump_diffs(composite.deltas, app, new_is_archived, type);
2916
2917}
2918
2919CMD(cdiff, "informative", "[--revision=REVISION [--revision=REVISION]] [PATH]...",
2920 "show current context diffs on stdout")
2921{
2922 do_diff(name, app, args, context_diff);
2923}
2924
2925CMD(diff, "informative", "[--revision=REVISION [--revision=REVISION]] [PATH]...",
2926 "show current unified diffs on stdout")
2927{
2928 do_diff(name, app, args, unified_diff);
2929}
2930
2931CMD(lca, "debug", "LEFT RIGHT", "print least common ancestor")
2932{
2933 if (args.size() != 2)
2934 throw usage(name);
2935
2936 revision_id anc, left, right;
2937
2938 complete(app, idx(args, 0)(), left);
2939 complete(app, idx(args, 1)(), right);
2940
2941 if (find_least_common_ancestor(left, right, anc, app))
2942 std::cout << anc << std::endl;
2943 else
2944 std::cout << "no common ancestor found" << std::endl;
2945}
2946
2947
2948CMD(lcad, "debug", "LEFT RIGHT", "print least common ancestor / dominator")
2949{
2950 if (args.size() != 2)
2951 throw usage(name);
2952
2953 revision_id anc, left, right;
2954
2955 complete(app, idx(args, 0)(), left);
2956 complete(app, idx(args, 1)(), right);
2957
2958 if (find_common_ancestor_for_merge(left, right, anc, app))
2959 std::cout << anc << std::endl;
2960 else
2961 std::cout << "no common ancestor/dominator found" << std::endl;
2962}
2963
2964
2965CMD(agraph, "debug", "", "dump ancestry graph to stdout in VCG format")
2966{
2967 set<revision_id> nodes;
2968 multimap<revision_id,string> branches;
2969
2970 std::multimap<revision_id, revision_id> edges_mmap;
2971 set<pair<revision_id, revision_id> > edges;
2972
2973 app.db.get_revision_ancestry(edges_mmap);
2974
2975 // convert from a weak lexicographic order to a strong one
2976 for (std::multimap<revision_id, revision_id>::const_iterator i = edges_mmap.begin();
2977 i != edges_mmap.end(); ++i)
2978 edges.insert(std::make_pair(i->first, i->second));
2979
2980 for (set<pair<revision_id, revision_id> >::const_iterator i = edges.begin();
2981 i != edges.end(); ++i)
2982 {
2983 nodes.insert(i->first);
2984 nodes.insert(i->second);
2985 }
2986
2987 vector< revision<cert> > certs;
2988 app.db.get_revision_certs(branch_cert_name, certs);
2989 for(vector< revision<cert> >::iterator i = certs.begin();
2990 i != certs.end(); ++i)
2991 {
2992 cert_value tv;
2993 decode_base64(i->inner().value, tv);
2994 revision_id tmp(i->inner().ident);
2995 nodes.insert(tmp); // in case no edges were connected
2996 branches.insert(make_pair(tmp, tv()));
2997 }
2998
2999
3000 cout << "graph: " << endl << "{" << endl; // open graph
3001 for (set<revision_id>::iterator i = nodes.begin(); i != nodes.end();
3002 ++i)
3003 {
3004 cout << "node: { title : \"" << *i << "\"\n"
3005 << " label : \"\\fb" << *i;
3006 pair<multimap<revision_id,string>::const_iterator,
3007 multimap<revision_id,string>::const_iterator> pair =
3008 branches.equal_range(*i);
3009 for (multimap<revision_id,string>::const_iterator j = pair.first;
3010 j != pair.second; ++j)
3011 {
3012 cout << "\\n\\fn" << j->second;
3013 }
3014 cout << "\"}" << endl;
3015 }
3016 for (set<pair<revision_id, revision_id> >::iterator i = edges.begin(); i != edges.end();
3017 ++i)
3018 {
3019 cout << "edge: { sourcename : \"" << i->first << "\"" << endl
3020 << " targetname : \"" << i->second << "\" }" << endl;
3021 }
3022 cout << "}" << endl << endl; // close graph
3023}
3024
3025
3026static void
3027write_file_targets(change_set const & cs,
3028 update_merge_provider & merger,
3029 app_state & app)
3030{
3031
3032 manifest_map files_to_write;
3033 for (change_set::delta_map::const_iterator i = cs.deltas.begin();
3034 i != cs.deltas.end(); ++i)
3035 {
3036 file_path pth(delta_entry_path(i));
3037 file_id ident(delta_entry_dst(i));
3038
3039 if (file_exists(pth))
3040 {
3041 hexenc<id> tmp_id;
3042 calculate_ident(pth, tmp_id, app.lua);
3043 if (tmp_id == ident.inner())
3044 continue;
3045 }
3046
3047 P(F("updating %s to %s\n") % pth % ident);
3048
3049 I(app.db.file_version_exists(ident)
3050 || merger.temporary_store.find(ident) != merger.temporary_store.end());
3051
3052 file_data tmp;
3053 if (app.db.file_version_exists(ident))
3054 app.db.get_file_version(ident, tmp);
3055 else if (merger.temporary_store.find(ident) != merger.temporary_store.end())
3056 tmp = merger.temporary_store[ident];
3057 write_localized_data(pth, tmp.inner(), app.lua);
3058 }
3059}
3060
3061
3062// static void dump_change_set(string const & name,
3063// change_set & cs)
3064// {
3065// data dat;
3066// write_change_set(cs, dat);
3067// cout << "change set '" << name << "'\n" << dat << endl;
3068// }
3069
3070CMD(update, "working copy", "\nREVISION", "update working copy to be based off another revision")
3071{
3072 manifest_map m_old, m_ancestor, m_working, m_chosen;
3073 manifest_id m_ancestor_id, m_chosen_id;
3074 revision_set r_old, r_working, r_new;
3075 revision_id r_old_id, r_chosen_id;
3076 change_set old_to_chosen, update;
3077
3078 if (args.size() != 0 && args.size() != 1)
3079 throw usage(name);
3080
3081 app.require_working_copy();
3082
3083 calculate_current_revision(app, r_working, m_old, m_working);
3084
3085 I(r_working.edges.size() == 1);
3086 r_old_id = edge_old_revision(r_working.edges.begin());
3087
3088 N(!null_id(r_old_id),
3089 F("this working directory is a new project; cannot update"));
3090
3091 if (args.size() == 0)
3092 {
3093 set<revision_id> candidates;
3094 pick_update_candidates(r_old_id, app, candidates);
3095 N(candidates.size() != 0,
3096 F("no candidates remain after selection"));
3097 if (candidates.size() != 1)
3098 {
3099 P(F("multiple update candidates:\n"));
3100 for (set<revision_id>::const_iterator i = candidates.begin();
3101 i != candidates.end(); ++i)
3102 P(F(" %s\n") % describe_revision(app, *i));
3103 P(F("choose one with 'monotone update <id>'\n"));
3104 N(false, F("multiple candidates remain after selection"));
3105 }
3106 r_chosen_id = *(candidates.begin());
3107 }
3108 else
3109 complete(app, idx(args, 0)(), r_chosen_id);
3110
3111 if (r_old_id == r_chosen_id)
3112 {
3113 P(F("already up to date at %s\n") % r_old_id);
3114 return;
3115 }
3116
3117 P(F("selected update target %s\n") % r_chosen_id);
3118 app.db.get_revision_manifest(r_chosen_id, m_chosen_id);
3119 app.db.get_manifest(m_chosen_id, m_chosen);
3120
3121 calculate_arbitrary_change_set(r_old_id, r_chosen_id, app, old_to_chosen);
3122
3123 update_merge_provider merger(app, m_old, m_chosen, m_working);
3124
3125 if (r_working.edges.size() == 0)
3126 {
3127 // working copy has no changes
3128 L(F("updating along chosen edge %s -> %s\n")
3129 % r_old_id % r_chosen_id);
3130 update = old_to_chosen;
3131 }
3132 else
3133 {
3134 change_set
3135 old_to_working(edge_changes(r_working.edges.begin())),
3136 working_to_merged,
3137 chosen_to_merged;
3138
3139 L(F("merging working copy with chosen edge %s -> %s\n")
3140 % r_old_id % r_chosen_id);
3141
3142 merge_change_sets(old_to_chosen,
3143 old_to_working,
3144 chosen_to_merged,
3145 working_to_merged,
3146 merger, app);
3147 // dump_change_set("chosen to merged", chosen_to_merged);
3148 // dump_change_set("working to merged", working_to_merged);
3149
3150 update = working_to_merged;
3151 }
3152
3153 local_path tmp_root((mkpath(book_keeping_dir) / mkpath("tmp")).string());
3154 if (directory_exists(tmp_root))
3155 delete_dir_recursive(tmp_root);
3156
3157 mkdir_p(tmp_root);
3158 apply_rearrangement_to_filesystem(update.rearrangement, tmp_root);
3159 write_file_targets(update, merger, app);
3160
3161 if (directory_exists(tmp_root))
3162 delete_dir_recursive(tmp_root);
3163
3164 // small race condition here...
3165 // nb: we write out r_chosen, not r_new, because the revision-on-disk
3166 // is the basis of the working copy, not the working copy itself.
3167 put_revision_id(r_chosen_id);
3168 P(F("updated to base revision %s\n") % r_chosen_id);
3169
3170 update_any_attrs(app);
3171}
3172
3173
3174
3175// this helper tries to produce merge <- mergeN(left,right); it searches
3176// for a common ancestor and if none is found synthesizes a common one with
3177// no contents. it then computes composite changesets via the common
3178// ancestor and does a 3-way merge.
3179
3180static void
3181try_one_merge(revision_id const & left_id,
3182 revision_id const & right_id,
3183 revision_id const & ancestor_id, // empty ==> use common ancestor
3184 revision_id & merged_id,
3185 app_state & app)
3186{
3187 revision_id anc_id;
3188 revision_set left_rev, right_rev, anc_rev, merged_rev;
3189
3190 app.db.get_revision(left_id, left_rev);
3191 app.db.get_revision(right_id, right_rev);
3192
3193 packet_db_writer dbw(app);
3194
3195 manifest_map anc_man, left_man, right_man, merged_man;
3196
3197 boost::shared_ptr<change_set>
3198 anc_to_left(new change_set()),
3199 anc_to_right(new change_set()),
3200 left_to_merged(new change_set()),
3201 right_to_merged(new change_set());
3202
3203 app.db.get_manifest(right_rev.new_manifest, right_man);
3204 app.db.get_manifest(left_rev.new_manifest, left_man);
3205
3206 // Make sure that we can't create malformed graphs where the left parent is
3207 // a descendent or ancestor of the right, or where both parents are equal,
3208 // etc.
3209 {
3210 set<revision_id> ids;
3211 ids.insert(left_id);
3212 ids.insert(right_id);
3213 erase_ancestors(ids, app);
3214 I(ids.size() == 2);
3215 }
3216
3217 if (!null_id(ancestor_id))
3218 {
3219 I(is_ancestor(ancestor_id, left_id, app));
3220 I(is_ancestor(ancestor_id, right_id, app));
3221
3222 anc_id = ancestor_id;
3223
3224 app.db.get_revision(anc_id, anc_rev);
3225 app.db.get_manifest(anc_rev.new_manifest, anc_man);
3226
3227 calculate_composite_change_set(anc_id, left_id, app, *anc_to_left);
3228 calculate_composite_change_set(anc_id, right_id, app, *anc_to_right);
3229 }
3230 else if (find_common_ancestor_for_merge(left_id, right_id, anc_id, app))
3231 {
3232 P(F("common ancestor %s found\n") % anc_id);
3233 P(F("trying 3-way merge\n"));
3234
3235 app.db.get_revision(anc_id, anc_rev);
3236 app.db.get_manifest(anc_rev.new_manifest, anc_man);
3237
3238 calculate_composite_change_set(anc_id, left_id, app, *anc_to_left);
3239 calculate_composite_change_set(anc_id, right_id, app, *anc_to_right);
3240 }
3241 else
3242 {
3243 P(F("no common ancestor found, synthesizing edges\n"));
3244 build_pure_addition_change_set(left_man, *anc_to_left);
3245 build_pure_addition_change_set(right_man, *anc_to_right);
3246 }
3247
3248 merge_provider merger(app, anc_man, left_man, right_man);
3249
3250 merge_change_sets(*anc_to_left, *anc_to_right,
3251 *left_to_merged, *right_to_merged,
3252 merger, app);
3253
3254 {
3255 // we have to record *some* route to this manifest. we pick the
3256 // smaller of the two.
3257 manifest_map tmp;
3258 apply_change_set(anc_man, *anc_to_left, tmp);
3259 apply_change_set(tmp, *left_to_merged, merged_man);
3260 calculate_ident(merged_man, merged_rev.new_manifest);
3261 base64< gzip<delta> > left_mdelta, right_mdelta;
3262 diff(left_man, merged_man, left_mdelta);
3263 diff(right_man, merged_man, right_mdelta);
3264 if (left_mdelta().size() < right_mdelta().size())
3265 dbw.consume_manifest_delta(left_rev.new_manifest,
3266 merged_rev.new_manifest, left_mdelta);
3267 else
3268 dbw.consume_manifest_delta(right_rev.new_manifest,
3269 merged_rev.new_manifest, right_mdelta);
3270 }
3271
3272 merged_rev.edges.insert(std::make_pair(left_id,
3273 std::make_pair(left_rev.new_manifest,
3274 left_to_merged)));
3275 merged_rev.edges.insert(std::make_pair(right_id,
3276 std::make_pair(right_rev.new_manifest,
3277 right_to_merged)));
3278 revision_data merged_data;
3279 write_revision_set(merged_rev, merged_data);
3280 calculate_ident(merged_data, merged_id);
3281 dbw.consume_revision_data(merged_id, merged_data);
3282 cert_revision_date_now(merged_id, app, dbw);
3283 cert_revision_author_default(merged_id, app, dbw);
3284}
3285
3286
3287CMD(merge, "tree", "", "merge unmerged heads of branch")
3288{
3289 set<revision_id> heads;
3290
3291 if (args.size() != 0)
3292 throw usage(name);
3293
3294 N(app.branch_name() != "",
3295 F("please specify a branch, with --branch=BRANCH"));
3296
3297 get_branch_heads(app.branch_name(), app, heads);
3298
3299 N(heads.size() != 0, F("branch '%s' is empty\n") % app.branch_name);
3300 N(heads.size() != 1, F("branch '%s' is merged\n") % app.branch_name);
3301
3302 set<revision_id>::const_iterator i = heads.begin();
3303 revision_id left = *i;
3304 revision_id ancestor;
3305 size_t count = 1;
3306 P(F("starting with revision 1 / %d\n") % heads.size());
3307 for (++i; i != heads.end(); ++i, ++count)
3308 {
3309 revision_id right = *i;
3310 P(F("merging with revision %d / %d\n") % (count + 1) % heads.size());
3311 P(F("[source] %s\n") % left);
3312 P(F("[source] %s\n") % right);
3313
3314 revision_id merged;
3315 transaction_guard guard(app.db);
3316 try_one_merge(left, right, revision_id(), merged, app);
3317
3318 // merged 1 edge; now we commit this, update merge source and
3319 // try next one
3320
3321 packet_db_writer dbw(app);
3322 cert_revision_in_branch(merged, app.branch_name(), app, dbw);
3323
3324 string log = (F("merge of %s\n"
3325 " and %s\n") % left % right).str();
3326 cert_revision_changelog(merged, log, app, dbw);
3327
3328 guard.commit();
3329 P(F("[merged] %s\n") % merged);
3330 left = merged;
3331 }
3332 P(F("your working copies have not been updated\n"));
3333}
3334
3335CMD(propagate, "tree", "SOURCE-BRANCH DEST-BRANCH",
3336 "merge from one branch to another asymmetrically")
3337{
3338 // this is a special merge operator, but very useful for people maintaining
3339 // "slightly disparate but related" trees. it does a one-way merge; less
3340 // powerful than putting things in the same branch and also more flexible.
3341 //
3342 // 1. check to see if src and dst branches are merged, if not abort, if so
3343 // call heads N1 and N2 respectively.
3344 //
3345 // 2. (FIXME: not yet present) run the hook propagate ("src-branch",
3346 // "dst-branch", N1, N2) which gives the user a chance to massage N1 into
3347 // a state which is likely to "merge nicely" with N2, eg. edit pathnames,
3348 // omit optional files of no interest.
3349 //
3350 // 3. do a normal 2 or 3-way merge on N1 and N2, depending on the
3351 // existence of common ancestors.
3352 //
3353 // 4. save the results as the delta (N2,M), the ancestry edges (N1,M)
3354 // and (N2,M), and the cert (N2,dst).
3355 //
3356 // there are also special cases we have to check for where no merge is
3357 // actually necessary, because there hasn't been any divergence since the
3358 // last time propagate was run.
3359
3360 set<revision_id> src_heads, dst_heads;
3361
3362 if (args.size() != 2)
3363 throw usage(name);
3364
3365 get_branch_heads(idx(args, 0)(), app, src_heads);
3366 get_branch_heads(idx(args, 1)(), app, dst_heads);
3367
3368 N(src_heads.size() != 0, F("branch '%s' is empty\n") % idx(args, 0)());
3369 N(src_heads.size() == 1, F("branch '%s' is not merged\n") % idx(args, 0)());
3370
3371 N(dst_heads.size() != 0, F("branch '%s' is empty\n") % idx(args, 1)());
3372 N(dst_heads.size() == 1, F("branch '%s' is not merged\n") % idx(args, 1)());
3373
3374 set<revision_id>::const_iterator src_i = src_heads.begin();
3375 set<revision_id>::const_iterator dst_i = dst_heads.begin();
3376
3377 P(F("propagating %s -> %s\n") % idx(args,0) % idx(args,1));
3378 P(F("[source] %s\n") % *src_i);
3379 P(F("[target] %s\n") % *dst_i);
3380
3381 // check for special cases
3382 if (*src_i == *dst_i || is_ancestor(*src_i, *dst_i, app))
3383 {
3384 P(F("branch '%s' is up-to-date with respect to branch '%s'\n")
3385 % idx(args, 1)() % idx(args, 0)());
3386 P(F("no action taken\n"));
3387 }
3388 else if (is_ancestor(*dst_i, *src_i, app))
3389 {
3390 P(F("no merge necessary; putting %s in branch '%s'\n")
3391 % (*src_i) % idx(args, 1)());
3392 transaction_guard guard(app.db);
3393 packet_db_writer dbw(app);
3394 cert_revision_in_branch(*src_i, idx(args, 1)(), app, dbw);
3395 guard.commit();
3396 }
3397 else
3398 {
3399 revision_id merged;
3400 transaction_guard guard(app.db);
3401 try_one_merge(*src_i, *dst_i, revision_id(), merged, app);
3402
3403 packet_db_writer dbw(app);
3404
3405 cert_revision_in_branch(merged, idx(args, 1)(), app, dbw);
3406
3407 string log = (F("propagate from branch '%s' (head %s)\n"
3408 " to branch '%s' (head %s)\n")
3409 % idx(args, 0) % (*src_i)
3410 % idx(args, 1) % (*dst_i)).str();
3411
3412 cert_revision_changelog(merged, log, app, dbw);
3413
3414 guard.commit();
3415 }
3416}
3417
3418CMD(explicit_merge, "tree", "LEFT-REVISION RIGHT-REVISION DEST-BRANCH\nLEFT-REVISION RIGHT-REVISION COMMON-ANCESTOR DEST-BRANCH",
3419 "merge two explicitly given revisions, placing result in given branch")
3420{
3421 revision_id left, right, ancestor;
3422 string branch;
3423
3424 if (args.size() != 3 && args.size() != 4)
3425 throw usage(name);
3426
3427 complete(app, idx(args, 0)(), left);
3428 complete(app, idx(args, 1)(), right);
3429 if (args.size() == 4)
3430 {
3431 complete(app, idx(args, 2)(), ancestor);
3432 N(is_ancestor(ancestor, left, app),
3433 F("%s is not an ancestor of %s") % ancestor % left);
3434 N(is_ancestor(ancestor, right, app),
3435 F("%s is not an ancestor of %s") % ancestor % right);
3436 branch = idx(args, 3)();
3437 }
3438 else
3439 {
3440 branch = idx(args, 2)();
3441 }
3442
3443 N(!(left == right),
3444 F("%s and %s are the same revision, aborting") % left % right);
3445 N(!is_ancestor(left, right, app),
3446 F("%s is already an ancestor of %s") % left % right);
3447 N(!is_ancestor(right, left, app),
3448 F("%s is already an ancestor of %s") % right % left);
3449
3450 // Somewhat redundant, but consistent with output of plain "merge" command.
3451 P(F("[source] %s\n") % left);
3452 P(F("[source] %s\n") % right);
3453
3454 revision_id merged;
3455 transaction_guard guard(app.db);
3456 try_one_merge(left, right, ancestor, merged, app);
3457
3458 packet_db_writer dbw(app);
3459
3460 cert_revision_in_branch(merged, branch, app, dbw);
3461
3462 string log = (F("explicit_merge of %s\n"
3463 " and %s\n"
3464 " using ancestor %s\n"
3465 " to branch '%s'\n")
3466 % left % right % ancestor % branch).str();
3467
3468 cert_revision_changelog(merged, log, app, dbw);
3469
3470 guard.commit();
3471 P(F("[merged] %s\n") % merged);
3472}
3473
3474CMD(complete, "informative", "(revision|manifest|file) PARTIAL-ID", "complete partial id")
3475{
3476 if (args.size() != 2)
3477 throw usage(name);
3478
3479 if (idx(args, 0)() == "revision")
3480 {
3481 N(idx(args, 1)().find_first_not_of("abcdef0123456789") == string::npos,
3482 F("non-hex digits in partial id"));
3483 set<revision_id> completions;
3484 app.db.complete(idx(args, 1)(), completions);
3485 for (set<revision_id>::const_iterator i = completions.begin();
3486 i != completions.end(); ++i)
3487 cout << i->inner()() << endl;
3488 }
3489 else if (idx(args, 0)() == "manifest")
3490 {
3491 N(idx(args, 1)().find_first_not_of("abcdef0123456789") == string::npos,
3492 F("non-hex digits in partial id"));
3493 set<manifest_id> completions;
3494 app.db.complete(idx(args, 1)(), completions);
3495 for (set<manifest_id>::const_iterator i = completions.begin();
3496 i != completions.end(); ++i)
3497 cout << i->inner()() << endl;
3498 }
3499 else if (idx(args, 0)() == "file")
3500 {
3501 N(idx(args, 1)().find_first_not_of("abcdef0123456789") == string::npos,
3502 F("non-hex digits in partial id"));
3503 set<file_id> completions;
3504 app.db.complete(idx(args, 1)(), completions);
3505 for (set<file_id>::const_iterator i = completions.begin();
3506 i != completions.end(); ++i)
3507 cout << i->inner()() << endl;
3508 }
3509 else
3510 throw usage(name);
3511}
3512
3513
3514CMD(revert, "working copy", "[PATH]...",
3515 "revert file(s), dir(s) or entire working copy")
3516{
3517 manifest_map m_old;
3518 revision_id old_revision_id;
3519 manifest_id old_manifest_id;
3520 change_set::path_rearrangement work, included, excluded;
3521 path_set old_paths;
3522
3523 app.require_working_copy();
3524
3525 calculate_base_revision(app,
3526 old_revision_id,
3527 old_manifest_id, m_old);
3528
3529 get_path_rearrangement(work);
3530 extract_path_set(m_old, old_paths);
3531
3532 path_set valid_paths(old_paths);
3533 extract_rearranged_paths(work, valid_paths);
3534 add_intermediate_paths(valid_paths);
3535
3536 app.set_restriction(valid_paths, args);
3537
3538 restrict_path_rearrangement(work, included, excluded, app);
3539
3540 for (manifest_map::const_iterator i = m_old.begin(); i != m_old.end(); ++i)
3541 {
3542 if (!app.restriction_includes(manifest_entry_path(i))) continue;
3543
3544 hexenc<id> ident;
3545
3546 if (file_exists(manifest_entry_path(i)))
3547 {
3548 calculate_ident(manifest_entry_path(i), ident, app.lua);
3549 // don't touch unchanged files
3550 if (manifest_entry_id(i) == ident) continue;
3551 }
3552
3553 L(F("reverting %s from %s to %s\n") %
3554 manifest_entry_path(i) % ident % manifest_entry_id(i));
3555
3556 N(app.db.file_version_exists(manifest_entry_id(i)),
3557 F("no file version %s found in database for %s")
3558 % manifest_entry_id(i) % manifest_entry_path(i));
3559
3560 file_data dat;
3561 L(F("writing file %s to %s\n")
3562 % manifest_entry_id(i) % manifest_entry_path(i));
3563 app.db.get_file_version(manifest_entry_id(i), dat);
3564 write_localized_data(manifest_entry_path(i), dat.inner(), app.lua);
3565 }
3566
3567 // race
3568 put_path_rearrangement(excluded);
3569 update_any_attrs(app);
3570}
3571
3572
3573CMD(rcs_import, "debug", "RCSFILE...",
3574 "import all versions in RCS files\n"
3575 "this command doesn't reconstruct revisions. you probably want cvs_import")
3576{
3577 if (args.size() < 1)
3578 throw usage(name);
3579
3580 transaction_guard guard(app.db);
3581 for (vector<utf8>::const_iterator i = args.begin();
3582 i != args.end(); ++i)
3583 {
3584 import_rcs_file(mkpath((*i)()), app.db);
3585 }
3586 guard.commit();
3587}
3588
3589
3590CMD(cvs_import, "rcs", "CVSROOT", "import all versions in CVS repository")
3591{
3592 if (args.size() != 1)
3593 throw usage(name);
3594
3595 import_cvs_repo(mkpath(idx(args, 0)()), app);
3596}
3597
3598static void
3599log_certs(app_state & app, revision_id id, cert_name name, string label, bool multiline)
3600{
3601 vector< revision<cert> > certs;
3602
3603 app.db.get_revision_certs(id, name, certs);
3604 erase_bogus_certs(certs, app);
3605 for (vector< revision<cert> >::const_iterator i = certs.begin();
3606 i != certs.end(); ++i)
3607 {
3608 cert_value tv;
3609 decode_base64(i->inner().value, tv);
3610 cout << label;
3611
3612 if (multiline)
3613 cout << endl << endl << tv << endl;
3614 else
3615 cout << tv << endl;
3616 }
3617}
3618
3619CMD(log, "informative", "[ID] [file]", "print history in reverse order starting from 'ID' (filtering by 'file')")
3620{
3621 revision_set rev;
3622 revision_id rid;
3623 set< pair<file_path, revision_id> > frontier;
3624 file_path file;
3625
3626 if (args.size() > 2)
3627 throw usage(name);
3628
3629 if (args.size() == 2)
3630 {
3631 complete(app, idx(args, 0)(), rid);
3632 file=file_path(idx(args, 1)());
3633 }
3634 else if (args.size() == 1)
3635 {
3636 std::string arg=idx(args, 0)();
3637 if (arg.find_first_not_of(constants::legal_id_bytes) == string::npos
3638 && arg.size()<=constants::idlen)
3639 {
3640 complete(app, arg, rid);
3641 }
3642 else
3643 {
3644 app.require_working_copy(); // no id arg, must have working copy
3645
3646 file=file_path(arg);
3647 file = file_path(arg);
3648 get_revision_id(rid);
3649 }
3650 }
3651 else
3652 {
3653 app.require_working_copy(); // no id arg, must have working copy
3654 get_revision_id(rid);
3655 }
3656
3657 frontier.insert(make_pair(file, rid));
3658
3659 cert_name author_name(author_cert_name);
3660 cert_name date_name(date_cert_name);
3661 cert_name branch_name(branch_cert_name);
3662 cert_name tag_name(tag_cert_name);
3663 cert_name changelog_name(changelog_cert_name);
3664 cert_name comment_name(comment_cert_name);
3665
3666 set<revision_id> seen;
3667 long depth = app.depth;
3668
3669 while(! frontier.empty() && (depth == -1 || depth > 0))
3670 {
3671 set< pair<file_path, revision_id> > next_frontier;
3672 for (set< pair<file_path, revision_id> >::const_iterator i = frontier.begin();
3673 i != frontier.end(); ++i)
3674 {
3675 file = i->first;
3676 rid = i->second;
3677
3678 bool print_this = file().empty();
3679 set< revision<id> > parents;
3680 vector< revision<cert> > tmp;
3681
3682 if (!app.db.revision_exists(rid))
3683 {
3684 L(F("revision %s does not exist in db, skipping\n") % rid);
3685 continue;
3686 }
3687
3688 if (seen.find(rid) != seen.end())
3689 continue;
3690
3691 seen.insert(rid);
3692
3693 app.db.get_revision(rid, rev);
3694
3695 changes_summary csum;
3696
3697 set<revision_id> ancestors;
3698
3699 for (edge_map::const_iterator e = rev.edges.begin();
3700 e != rev.edges.end(); ++e)
3701 {
3702 ancestors.insert(edge_old_revision(e));
3703
3704 change_set const & cs = edge_changes(e);
3705 if (! file().empty())
3706 {
3707 if (cs.rearrangement.has_deleted_file(file) ||
3708 cs.rearrangement.has_renamed_file_src(file))
3709 {
3710 print_this = false;
3711 next_frontier.clear();
3712 break;
3713 }
3714 else
3715 {
3716 file_path old_file = apply_change_set_inverse(cs, file);
3717 L(F("revision '%s' in '%s' maps to '%s' in %s\n")
3718 % rid % file % old_file % edge_old_revision(e));
3719 if (!(old_file == file) ||
3720 cs.deltas.find(file) != cs.deltas.end())
3721 {
3722 file = old_file;
3723 print_this = true;
3724 }
3725 }
3726 }
3727 next_frontier.insert(std::make_pair(file, edge_old_revision(e)));
3728
3729 csum.add_change_set(cs);
3730 }
3731
3732 if (print_this)
3733 {
3734 cout << "-----------------------------------------------------------------"
3735 << endl;
3736 cout << "Revision: " << rid << endl;
3737
3738 for (set<revision_id>::const_iterator anc = ancestors.begin();
3739 anc != ancestors.end(); ++anc)
3740 cout << "Ancestor: " << *anc << endl;
3741
3742 log_certs(app, rid, author_name, "Author: ", false);
3743 log_certs(app, rid, date_name, "Date: ", false);
3744 log_certs(app, rid, branch_name, "Branch: ", false);
3745 log_certs(app, rid, tag_name, "Tag: ", false);
3746
3747 if (! csum.empty)
3748 {
3749 cout << endl;
3750 csum.print(cout, 70);
3751 cout << endl;
3752 }
3753
3754 log_certs(app, rid, changelog_name, "ChangeLog: ", true);
3755 log_certs(app, rid, comment_name, "Comments: ", true);
3756 }
3757 }
3758 frontier = next_frontier;
3759 if (depth > 0)
3760 {
3761 depth--;
3762 }
3763 }
3764}
3765
3766
3767CMD(setup, "tree", "DIRECTORY", "setup a new working copy directory")
3768{
3769 string dir;
3770
3771 if (args.size() != 1)
3772 throw usage(name);
3773
3774 dir = idx(args,0)();
3775 app.create_working_copy(dir);
3776 revision_id null;
3777 put_revision_id(null);
3778}
3779
3780CMD(automate, "automation",
3781 "interface_version\n"
3782 "heads BRANCH\n"
3783 "descendents REV1 [REV2 [REV3 [...]]]\n"
3784 "erase_ancestors [REV1 [REV2 [REV3 [...]]]]\n"
3785 "toposort [REV1 [REV2 [REV3 [...]]]]\n"
3786 "ancestry_difference NEW_REV [OLD_REV1 [OLD_REV2 [...]]]\n"
3787 "leaves",
3788 "automation interface")
3789{
3790 if (args.size() == 0)
3791 throw usage(name);
3792
3793 vector<utf8>::const_iterator i = args.begin();
3794 utf8 cmd = *i;
3795 ++i;
3796 vector<utf8> cmd_args(i, args.end());
3797
3798 automate_command(cmd, cmd_args, name, app, cout);
3799}
3800
3801CMD(set, "vars", "DOMAIN NAME VALUE",
3802 "set the database variable NAME to VALUE, in domain DOMAIN")
3803{
3804 if (args.size() != 3)
3805 throw usage(name);
3806
3807 var_domain d;
3808 var_name n;
3809 var_value v;
3810 internalize_var_domain(idx(args, 0), d);
3811 n = var_name(idx(args, 1)());
3812 v = var_value(idx(args, 2)());
3813 app.db.set_var(std::make_pair(d, n), v);
3814}
3815
3816CMD(unset, "vars", "DOMAIN NAME",
3817 "remove the database variable NAME in domain DOMAIN")
3818{
3819 if (args.size() != 2)
3820 throw usage(name);
3821
3822 var_domain d;
3823 var_name n;
3824 internalize_var_domain(idx(args, 0), d);
3825 n = var_name(idx(args, 1)());
3826 var_key k(d, n);
3827 N(app.db.var_exists(k), F("no var with name %s in domain %s") % n % d);
3828 app.db.clear_var(k);
3829}
3830
3831}; // namespace commands

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status