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 <cstdio>
8#include <set>
9#include <vector>
10#include <algorithm>
11#include <iterator>
12#include <boost/filesystem/path.hpp>
13#include <boost/filesystem/operations.hpp>
14#include <boost/lexical_cast.hpp>
15
16#include "commands.hh"
17#include "constants.hh"
18
19#include "app_state.hh"
20#include "diff_patch.hh"
21#include "file_io.hh"
22#include "keys.hh"
23#include "manifest.hh"
24#include "netsync.hh"
25#include "network.hh"
26#include "packet.hh"
27#include "patch_set.hh"
28#include "rcs_import.hh"
29#include "sanity.hh"
30#include "cert.hh"
31#include "transforms.hh"
32#include "ui.hh"
33#include "update.hh"
34#include "vocab.hh"
35#include "work.hh"
36
37//
38// this file defines the task-oriented "top level" commands which can be
39// issued as part of a monotone command line. the command line can only
40// have one such command on it, followed by a vector of strings which are its
41// arguments. all --options will be processed by the main program *before*
42// calling a command
43//
44// we might expose this blunt command interface to scripting someday. but
45// not today.
46
47namespace commands
48{
49 struct command;
50 bool operator<(command const & self, command const & other);
51};
52
53namespace std
54{
55 template <>
56 struct std::greater<commands::command *>
57 {
58 bool operator()(commands::command const * a, commands::command const * b)
59 {
60 return *a < *b;
61 }
62 };
63};
64
65namespace commands
66{
67 using namespace std;
68
69 struct command;
70
71 static map<string,command *> cmds;
72
73 struct command
74 {
75 string name;
76 string cmdgroup;
77 string params;
78 string desc;
79 command(string const & n,
80 string const & g,
81 string const & p,
82 string const & d) : name(n), cmdgroup(g), params(p), desc(d)
83 { cmds[n] = this; }
84 virtual ~command() {}
85 virtual void exec(app_state & app, vector<utf8> const & args) = 0;
86 };
87
88 bool operator<(command const & self, command const & other)
89 {
90 return ((self.cmdgroup < other.cmdgroup)
91 || ((self.cmdgroup == other.cmdgroup) && (self.name < other.name)));
92 }
93
94
95 void explain_usage(string const & cmd, ostream & out)
96 {
97 map<string,command *>::const_iterator i;
98 i = cmds.find(cmd);
99 if (i != cmds.end())
100 {
101string params = i->second->params;
102int old = 0;
103int j = params.find('\n');
104while (j != -1)
105 {
106 out << " " << i->second->name
107<< " " << params.substr(old, j - old)
108<< endl;
109 old = j + 1;
110 j = params.find('\n', old);
111 }
112out << " " << i->second->name
113 << " " << params.substr(old, j - old)
114 << endl
115 << " " << i->second->desc << endl << endl;
116return;
117 }
118
119 vector<command *> sorted;
120 out << "commands:" << endl;
121 for (i = cmds.begin(); i != cmds.end(); ++i)
122 {
123sorted.push_back(i->second);
124 }
125
126 sort(sorted.begin(), sorted.end(), std::greater<command *>());
127
128 string curr_group;
129 size_t col = 0;
130 size_t col2 = 0;
131 for (size_t i = 0; i < sorted.size(); ++i)
132 {
133col2 = col2 > idx(sorted, i)->cmdgroup.size() ? col2 : idx(sorted, i)->cmdgroup.size();
134 }
135
136 for (size_t i = 0; i < sorted.size(); ++i)
137 {
138if (idx(sorted, i)->cmdgroup != curr_group)
139 {
140 curr_group = idx(sorted, i)->cmdgroup;
141 out << endl;
142 out << " " << idx(sorted, i)->cmdgroup;
143 col = idx(sorted, i)->cmdgroup.size() + 2;
144 while (col++ < (col2 + 3))
145 out << ' ';
146 }
147out << " " << idx(sorted, i)->name;
148col += idx(sorted, i)->name.size() + 1;
149if (col >= 70)
150 {
151 out << endl;
152 col = 0;
153 while (col++ < (col2 + 3))
154 out << ' ';
155 }
156 }
157 out << endl << endl;
158 }
159
160 int process(app_state & app, string const & cmd, vector<utf8> const & args)
161 {
162 if (cmds.find(cmd) != cmds.end())
163 {
164L(F("executing %s command\n") % cmd);
165cmds[cmd]->exec(app, args);
166return 0;
167 }
168 else
169 {
170ui.inform(F("unknown command '%s'\n") % cmd);
171return 1;
172 }
173 }
174
175#define CMD(C, group, params, desc) \
176struct cmd_ ## C : public command \
177{ \
178 cmd_ ## C() : command(#C, group, params, desc) \
179 {} \
180 virtual void exec(app_state & app, \
181 vector<utf8> const & args); \
182}; \
183static cmd_ ## C C ## _cmd; \
184void cmd_ ## C::exec(app_state & app, \
185 vector<utf8> const & args) \
186
187#define ALIAS(C, realcommand, group, params, desc)\
188CMD(C, group, params, desc)\
189{\
190 process(app, string(#realcommand), args);\
191}
192
193static bool bookdir_exists()
194{
195 return directory_exists(local_path(book_keeping_dir));
196}
197
198static void ensure_bookdir()
199{
200 mkdir_p(local_path(book_keeping_dir));
201}
202
203static void get_manifest_path(local_path & m_path)
204{
205 m_path = (mkpath(book_keeping_dir) / mkpath(manifest_file_name)).string();
206 L(F("manifest path is %s\n") % m_path);
207}
208
209static void get_work_path(local_path & w_path)
210{
211 w_path = (mkpath(book_keeping_dir) / mkpath(work_file_name)).string();
212 L(F("work path is %s\n") % w_path);
213}
214
215static void get_manifest_map(manifest_map & m)
216{
217 ensure_bookdir();
218 local_path m_path;
219 base64< gzip<data> > m_data;
220 get_manifest_path(m_path);
221 if (file_exists(m_path))
222 {
223 L(F("loading manifest file %s\n") % m_path);
224 read_data(m_path, m_data);
225 read_manifest_map(manifest_data(m_data), m);
226 L(F("read %d manifest entries\n") % m.size());
227 }
228 else
229 {
230 L(F("no manifest file %s\n") % m_path);
231 }
232}
233
234static void put_manifest_map(manifest_map const & m)
235{
236 ensure_bookdir();
237 local_path m_path;
238 manifest_data m_data;
239 get_manifest_path(m_path);
240 L(F("writing manifest file %s\n") % m_path);
241 write_manifest_map(m, m_data);
242 write_data(m_path, m_data.inner());
243 L(F("wrote %d manifest entries\n") % m.size());
244}
245
246static void get_work_set(work_set & w)
247{
248 ensure_bookdir();
249 local_path w_path;
250 get_work_path(w_path);
251 if (file_exists(w_path))
252 {
253 L(F("checking for un-committed work file %s\n") % w_path);
254 data w_data;
255 read_data(w_path, w_data);
256 read_work_set(w_data, w);
257 L(F("read %d dels, %d adds, %d renames from %s\n") %
258w.dels.size() % w.adds.size() % w.renames.size() % w_path);
259 }
260 else
261 {
262 L(F("no un-committed work file %s\n") % w_path);
263 }
264}
265
266static void remove_work_set()
267{
268 local_path w_path;
269 get_work_path(w_path);
270 if (file_exists(w_path))
271 delete_file(w_path);
272}
273
274static void put_work_set(work_set & w)
275{
276 local_path w_path;
277 get_work_path(w_path);
278
279 if (w.adds.size() > 0
280 || w.dels.size() > 0
281 || w.renames.size() > 0)
282 {
283 ensure_bookdir();
284 data w_data;
285 write_work_set(w_data, w);
286 write_data(w_path, w_data);
287 }
288 else
289 {
290 delete_file(w_path);
291 }
292}
293
294static void update_any_attrs(app_state & app)
295{
296 file_path fp;
297 data attr_data;
298 attr_map attr;
299
300 get_attr_path(fp);
301 if (!file_exists(fp))
302 return;
303
304 read_data(fp, attr_data);
305 read_attr_map(attr_data, attr);
306 apply_attributes(app, attr);
307}
308
309static void calculate_new_manifest_map(manifest_map const & m_old,
310 manifest_map & m_new,
311 rename_set & renames,
312 app_state & app)
313{
314 path_set paths;
315 work_set work;
316 extract_path_set(m_old, paths);
317 get_work_set(work);
318 if (work.dels.size() > 0)
319 L(F("removing %d dead files from manifest\n") %
320 work.dels.size());
321 if (work.adds.size() > 0)
322 L(F("adding %d files to manifest\n") %
323 work.adds.size());
324 if (work.renames.size() > 0)
325 L(F("renaming %d files in manifest\n") %
326 work.renames.size());
327 apply_work_set(work, paths);
328 build_manifest_map(paths, m_new, app);
329 renames = work.renames;
330}
331
332
333static void calculate_new_manifest_map(manifest_map const & m_old,
334 manifest_map & m_new,
335 app_state & app)
336{
337 rename_set dummy;
338 calculate_new_manifest_map (m_old, m_new, dummy, app);
339}
340
341
342static string get_stdin()
343{
344 char buf[constants::bufsz];
345 string tmp;
346 while(cin)
347 {
348 cin.read(buf, constants::bufsz);
349 tmp.append(buf, cin.gcount());
350 }
351 return tmp;
352}
353
354static void get_log_message(patch_set const & ps,
355 app_state & app,
356 string & log_message)
357{
358 string commentary;
359 string summary;
360 stringstream ss;
361 patch_set_to_text_summary(ps, ss);
362 summary = ss.str();
363 commentary += "----------------------------------------------------------------------\n";
364 commentary += "Enter Log. Lines beginning with `MT:' are removed automatically\n";
365 commentary += "\n";
366 commentary += summary;
367 commentary += "----------------------------------------------------------------------\n";
368 N(app.lua.hook_edit_comment(commentary, log_message),
369 F("edit of log message failed"));
370}
371
372template <typename ID>
373static void complete(app_state & app,
374 string const & str,
375 ID & completion)
376{
377 N(str.find_first_not_of(constants::legal_id_bytes) == string::npos,
378 F("non-hex digits in id"));
379 if (str.size() == constants::idlen)
380 {
381 completion = ID(str);
382 return;
383 }
384 set<ID> completions;
385 app.db.complete(str, completions);
386 N(completions.size() != 0,
387 F("partial id '%s' does not have a unique expansion") % str);
388 if (completions.size() > 1)
389 {
390 string err = (F("partial id '%s' has multiple ambiguous expansions: \n") % str).str();
391 for (typename set<ID>::const_iterator i = completions.begin();
392 i != completions.end(); ++i)
393err += (i->inner()() + "\n");
394 N(completions.size() == 1, err);
395 }
396 completion = *(completions.begin());
397 P(F("expanded partial id '%s' to '%s'\n") % str % completion);
398}
399
400
401static void find_oldest_ancestors(manifest_id const & child,
402 set<manifest_id> & ancs,
403 app_state & app)
404{
405 cert_name tn(ancestor_cert_name);
406 ancs.insert(child);
407 set<manifest_id> seen;
408 while (true)
409 {
410 set<manifest_id> next_frontier;
411 for (set<manifest_id>::const_iterator i = ancs.begin();
412 i != ancs.end(); ++i)
413{
414 if (seen.find(*i) != seen.end())
415 continue;
416 vector< manifest<cert> > tmp;
417 app.db.get_manifest_certs(*i, tn, tmp);
418 erase_bogus_certs(tmp, app);
419 for (vector< manifest<cert> >::const_iterator j = tmp.begin();
420 j != tmp.end(); ++j)
421 {
422 cert_value tv;
423 decode_base64(j->inner().value, tv);
424 manifest_id anc_id (tv());
425 next_frontier.insert(anc_id);
426 }
427 seen.insert(*i);
428}
429
430 if (next_frontier.empty())
431break;
432 else
433ancs = next_frontier;
434 }
435}
436
437// the goal here is to look back through the ancestry of the provided
438// child, checking to see the least ancestor it has which we received from
439// the given network url.
440//
441// we use the ancestor as the source manifest when building a patchset to
442// send to that url.
443
444static bool find_ancestor_on_netserver (manifest_id const & child,
445url const & u,
446manifest_id & anc,
447app_state & app)
448{
449 set<manifest_id> frontier;
450 cert_name tn(ancestor_cert_name);
451 frontier.insert(child);
452
453 while (!frontier.empty())
454 {
455 set<manifest_id> next_frontier;
456 for (set<manifest_id>::const_iterator i = frontier.begin();
457 i != frontier.end(); ++i)
458{
459 vector< manifest<cert> > tmp;
460 app.db.get_manifest_certs(*i, tn, tmp);
461 erase_bogus_certs(tmp, app);
462
463 // we go through this vector backwards because we would prefer to
464 // hit more recently-queued ancestors (such as intermediate nodes
465 // in a multi-node merge) rather than older ancestors. but of
466 // course, any ancestor will do.
467
468 for (vector< manifest<cert> >::reverse_iterator j = tmp.rbegin();
469 j != tmp.rend(); ++j)
470 {
471 cert_value tv;
472 decode_base64(j->inner().value, tv);
473 manifest_id anc_id (tv());
474
475 L(F("looking for parent %s of %s on server\n") % (*i) % anc_id);
476
477 if (app.db.manifest_exists_on_netserver (u, anc_id))
478{
479 L(F("found parent %s on server\n") % anc_id);
480 anc = anc_id;
481 return true;
482}
483 else
484next_frontier.insert(anc_id);
485 }
486}
487
488 frontier = next_frontier;
489 }
490
491 return false;
492}
493
494
495static void queue_edge_for_target_ancestor (url const & targ,
496 manifest_id const & child_id,
497 manifest_map const & child_map,
498 app_state & app)
499{
500 // now here is an interesting thing: we *might* be sending data to a
501 // depot, or someone with indeterminate pre-existing state (say the first
502 // time we post to netnews), therefore we cannot just "send the edge" we
503 // just constructed in a merge or commit, we need to send an edge from a
504 // parent which we know to be present in the depot (or else an edge from
505 // the empty map -- full contents of all files). what is sent therefore
506 // changes on a depot-by-depot basis. this function calculates the
507 // appropriate thing to send.
508 //
509 // nb: this has no direct relation to what we store in our own
510 // database. we always store the edge from our parent, and we always know
511 // when we have a parent.
512
513 set<url> one_target;
514 one_target.insert(targ);
515 queueing_packet_writer qpw(app, one_target);
516
517 manifest_id targ_ancestor_id;
518
519 if (find_ancestor_on_netserver (child_id,
520 targ,
521 targ_ancestor_id,
522 app))
523 {
524 // write everything from there to here
525 reverse_queue rq(app.db);
526 qpw.rev.reset(rq);
527 write_ancestry_paths(targ_ancestor_id, child_id, app, qpw);
528 qpw.rev.reset();
529 }
530 else
531 {
532 // or just write a complete version of "here"
533 manifest_map empty_manifest;
534 patch_set ps;
535 manifests_to_patch_set(empty_manifest, child_map, app, ps);
536 patch_set_to_packets(ps, app, qpw);
537 }
538
539 // now that we've queued the data, we can note this new child
540 // node as existing (well .. soon-to-exist) on the server
541 app.db.note_manifest_on_netserver (targ, child_id);
542
543}
544
545
546// this helper tries to produce merge <- mergeN(left,right), possibly
547// merge3 if it can find an ancestor, otherwise merge2. it also queues the
548// appropriate edges from known ancestors to the new merge node, to be
549// transmitted to each of the targets provided.
550
551static void try_one_merge(manifest_id const & left,
552 manifest_id const & right,
553 manifest_id & merged,
554 app_state & app,
555 set<url> const & targets)
556{
557 manifest_data left_data, right_data, ancestor_data, merged_data;
558 manifest_map left_map, right_map, ancestor_map, merged_map;
559 manifest_id ancestor;
560 rename_edge left_renames, right_renames;
561
562 app.db.get_manifest_version(left, left_data);
563 app.db.get_manifest_version(right, right_data);
564 read_manifest_map(left_data, left_map);
565 read_manifest_map(right_data, right_map);
566
567 simple_merge_provider merger(app);
568
569 if(find_common_ancestor(left, right, ancestor, app))
570 {
571 P(F("common ancestor %s found, trying 3-way merge\n") % ancestor);
572 app.db.get_manifest_version(ancestor, ancestor_data);
573 read_manifest_map(ancestor_data, ancestor_map);
574 N(merge3(ancestor_map, left_map, right_map,
575 app, merger, merged_map, left_renames.mapping, right_renames.mapping),
576F("failed to 3-way merge manifests %s and %s via ancestor %s")
577% left % right % ancestor);
578 }
579 else
580 {
581 P(F("no common ancestor found, trying 2-way merge\n"));
582 N(merge2(left_map, right_map, app, merger, merged_map),
583F("failed to 2-way merge manifests %s and %s") % left % right);
584 }
585
586 write_manifest_map(merged_map, merged_data);
587 calculate_ident(merged_map, merged);
588
589 base64< gzip<delta> > left_edge;
590 diff(left_data.inner(), merged_data.inner(), left_edge);
591
592 // FIXME: we do *not* manufacture or store the second edge to
593 // the merged version, since doing so violates the
594 // assumptions of the db, and the 'right' version already
595 // exists in its entirety, anyways. this is a subtle issue
596 // though and I'm not sure I'm making the right
597 // decision. revisit. if you do not see that it is a subtle
598 // issue I suggest you are not thinking about it long enough.
599 //
600 // base64< gzip<delta> > right_edge;
601 // diff(right_data.inner(), merged_data.inner(), right_edge);
602 // app.db.put_manifest_version(right, merged, right_edge);
603
604
605 // we do of course record the left edge, and ancestry relationship to
606 // both predecessors.
607
608 {
609 packet_db_writer dbw(app);
610
611 dbw.consume_manifest_delta(left, merged, left_edge);
612 cert_manifest_ancestor(left, merged, app, dbw);
613 cert_manifest_ancestor(right, merged, app, dbw);
614 cert_manifest_date_now(merged, app, dbw);
615 cert_manifest_author_default(merged, app, dbw);
616
617 left_renames.parent = left;
618 left_renames.child = merged;
619 right_renames.parent = right;
620 right_renames.child = merged;
621 cert_manifest_rename(merged, left_renames, app, dbw);
622 cert_manifest_rename(merged, right_renames, app, dbw);
623
624 // make sure the appropriate edges get queued for the network.
625 for (set<url>::const_iterator targ = targets.begin();
626 targ != targets.end(); ++targ)
627 {
628queue_edge_for_target_ancestor (*targ, merged, merged_map, app);
629 }
630
631 // throw in all available certs for good measure
632 queueing_packet_writer qpw(app, targets);
633 vector< manifest<cert> > certs;
634 app.db.get_manifest_certs(merged, certs);
635 for(vector< manifest<cert> >::const_iterator i = certs.begin();
636i != certs.end(); ++i)
637 qpw.consume_manifest_cert(*i);
638 }
639
640}
641
642
643// actual commands follow
644
645
646static void ls_certs (string name, app_state & app, vector<utf8> const & args)
647{
648 if (args.size() != 2)
649 throw usage(name);
650
651 vector<cert> certs;
652
653 transaction_guard guard(app.db);
654
655 if (idx(args, 0)() == "manifest")
656 {
657 manifest_id ident;
658 complete(app, idx(args, 1)(), ident);
659 vector< manifest<cert> > ts;
660 app.db.get_manifest_certs(ident, ts);
661 for (size_t i = 0; i < ts.size(); ++i)
662certs.push_back(idx(ts, i).inner());
663 }
664 else if (idx(args, 0)() == "file")
665 {
666 file_id ident;
667 complete(app, idx(args, 1)(), ident);
668 vector< file<cert> > ts;
669 app.db.get_file_certs(ident, ts);
670 for (size_t i = 0; i < ts.size(); ++i)
671certs.push_back(idx(ts, i).inner());
672 }
673 else
674 throw usage(name);
675
676 {
677 set<rsa_keypair_id> checked;
678 for (size_t i = 0; i < certs.size(); ++i)
679 {
680if (checked.find(idx(certs, i).key) == checked.end() &&
681 !app.db.public_key_exists(idx(certs, i).key))
682 P(F("warning: no public key '%s' found in database\n")
683 % idx(certs, i).key);
684checked.insert(idx(certs, i).key);
685 }
686 }
687
688 for (size_t i = 0; i < certs.size(); ++i)
689 {
690 bool ok = check_cert(app, idx(certs, i));
691 cert_value tv;
692 decode_base64(idx(certs, i).value, tv);
693 string washed;
694 if (guess_binary(tv())
695 || idx(certs, i).name == rename_cert_name)
696{
697 washed = "<binary data>";
698}
699 else
700{
701 washed = tv();
702}
703 string head = string(ok ? "ok sig from " : "bad sig from ")
704+ "[" + idx(certs, i).key() + "] : "
705+ "[" + idx(certs, i).name() + "] = [";
706 string pad(head.size(), ' ');
707 vector<string> lines;
708 split_into_lines(washed, lines);
709 I(lines.size() > 0);
710 cout << head << idx(lines, 0) ;
711 for (size_t i = 1; i < lines.size(); ++i)
712cout << endl << pad << idx(lines, i);
713 cout << "]" << endl;
714 }
715 guard.commit();
716}
717
718static void ls_keys (string name, app_state & app, vector<utf8> const & args)
719{
720 vector<rsa_keypair_id> pubkeys;
721 vector<rsa_keypair_id> privkeys;
722
723 transaction_guard guard(app.db);
724
725 if (args.size() == 0)
726 app.db.get_key_ids("", pubkeys, privkeys);
727 else if (args.size() == 1)
728 app.db.get_key_ids(idx(args, 0)(), pubkeys, privkeys);
729 else
730 throw usage(name);
731
732 if (pubkeys.size() > 0)
733 {
734 cout << endl << "[public keys]" << endl;
735 for (size_t i = 0; i < pubkeys.size(); ++i)
736{
737 rsa_keypair_id keyid = idx(pubkeys, i)();
738 base64<rsa_pub_key> pub_encoded;
739 hexenc<id> hash_code;
740
741 app.db.get_key(keyid, pub_encoded);
742 key_hash_code(keyid, pub_encoded, hash_code);
743 cout << hash_code << " " << keyid << endl;
744}
745 cout << endl;
746 }
747
748 if (privkeys.size() > 0)
749 {
750 cout << endl << "[private keys]" << endl;
751 for (size_t i = 0; i < privkeys.size(); ++i)
752{
753 rsa_keypair_id keyid = idx(privkeys, i)();
754 base64< arc4<rsa_priv_key> > priv_encoded;
755 hexenc<id> hash_code;
756 app.db.get_key(keyid, priv_encoded);
757 key_hash_code(keyid, priv_encoded, hash_code);
758 cout << hash_code << " " << keyid << endl;
759}
760 cout << endl;
761 }
762
763 if (pubkeys.size() == 0 &&
764 privkeys.size() == 0)
765 P(F("warning: no keys found matching '%s'\n") % idx(args, 0)());
766
767 guard.commit();
768}
769
770CMD(genkey, "key and cert", "KEYID", "generate an RSA key-pair")
771{
772 if (args.size() != 1)
773 throw usage(name);
774
775 transaction_guard guard(app.db);
776 rsa_keypair_id ident;
777 internalize_rsa_keypair_id(idx(args, 0), ident);
778
779 N(! app.db.key_exists(ident),
780 F("key '%s' already exists in database") % ident);
781
782 base64<rsa_pub_key> pub;
783 base64< arc4<rsa_priv_key> > priv;
784 P(F("generating key-pair '%s'\n") % ident);
785 generate_key_pair(app.lua, ident, pub, priv);
786 P(F("storing key-pair '%s' in database\n") % ident);
787 app.db.put_key_pair(ident, pub, priv);
788
789 guard.commit();
790}
791
792CMD(cert, "key and cert", "(file|manifest) ID CERTNAME [CERTVAL]",
793 "create a cert for a file or manifest")
794{
795 if ((args.size() != 4) && (args.size() != 3))
796 throw usage(name);
797
798 transaction_guard guard(app.db);
799
800 hexenc<id> ident;
801 if (idx(args, 0)() == "manifest")
802 {
803 manifest_id mid;
804 complete(app, idx(args, 1)(), mid);
805 ident = mid.inner();
806 }
807 else if (idx(args, 0)() == "file")
808 {
809 file_id fid;
810 complete(app, idx(args, 1)(), fid);
811 ident = fid.inner();
812 }
813 else
814 throw usage(this->name);
815
816 cert_name name;
817 internalize_cert_name(idx(args, 2), name);
818
819 rsa_keypair_id key;
820 if (app.signing_key() != "")
821 key = app.signing_key;
822 else
823 N(guess_default_key(key, app),
824 F("no unique private key found, and no key specified"));
825
826 cert_value val;
827 if (args.size() == 4)
828 val = cert_value(idx(args, 3)());
829 else
830 val = cert_value(get_stdin());
831
832 base64<cert_value> val_encoded;
833 encode_base64(val, val_encoded);
834
835 cert t(ident, name, val_encoded, key);
836
837 set<url> targets;
838 cert_value branchname;
839 guess_branch (manifest_id(ident), app, branchname);
840 app.lua.hook_get_post_targets(branchname(), targets);
841
842 queueing_packet_writer qpw(app, targets);
843 packet_db_writer dbw(app);
844
845 // nb: we want to throw usage on mis-use *before* asking for a
846 // passphrase.
847
848 if (idx(args, 0)() == "file")
849 {
850 calculate_cert(app, t);
851 dbw.consume_file_cert(file<cert>(t));
852 qpw.consume_file_cert(file<cert>(t));
853 }
854 else if (idx(args, 0)() == "manifest")
855 {
856 calculate_cert(app, t);
857 dbw.consume_manifest_cert(manifest<cert>(t));
858 qpw.consume_manifest_cert(manifest<cert>(t));
859 }
860 else
861 throw usage(this->name);
862
863 guard.commit();
864}
865
866CMD(vcheck, "key and cert", "create [MANIFEST]\ncheck [MANIFEST]",
867 "create or check a cryptographic version-check certificate")
868{
869 if (args.size() < 1 || args.size() > 2)
870 throw usage(name);
871
872 set<manifest_id> ids;
873 if (args.size() == 1)
874 {
875 N(app.branch_name() != "", F("need --branch argument for branch-based vcheck"));
876 get_branch_heads(app.branch_name(), app, ids);
877 }
878 else
879 {
880 for (size_t i = 1; i < args.size(); ++i)
881{
882 manifest_id mid;
883 complete(app, idx(args, i)(), mid);
884 ids.insert(mid);
885}
886 }
887
888 if (idx(args, 0)() == "create")
889 for (set<manifest_id>::const_iterator i = ids.begin();
890 i != ids.end(); ++i)
891 {
892 packet_db_writer dbw(app);
893 cert_manifest_vcheck(*i, app, dbw);
894 }
895
896 else if (idx(args, 0)() == "check")
897 for (set<manifest_id>::const_iterator i = ids.begin();
898 i != ids.end(); ++i)
899 {
900 check_manifest_vcheck(*i, app);
901 }
902
903 else
904 throw usage(name);
905}
906
907
908CMD(tag, "certificate", "ID TAGNAME",
909 "put a symbolic tag cert on a manifest version")
910{
911 if (args.size() != 2)
912 throw usage(name);
913 manifest_id m;
914 complete(app, idx(args, 0)(), m);
915 packet_db_writer dbw(app);
916
917 set<url> targets;
918 cert_value branchname;
919 guess_branch(m, app, branchname);
920 app.lua.hook_get_post_targets(branchname(), targets);
921
922 queueing_packet_writer qpw(app, targets);
923 cert_manifest_tag(m, idx(args, 1)(), app, dbw);
924 cert_manifest_tag(m, idx(args, 1)(), app, qpw);
925}
926
927CMD(testresult, "certificate", "ID (true|false)",
928 "note the results of running a test on a manifest")
929{
930 if (args.size() != 2)
931 throw usage(name);
932 manifest_id m;
933 complete(app, idx(args, 0)(), m);
934 set<url> targets;
935 cert_value branchname;
936 guess_branch(m, app, branchname);
937 app.lua.hook_get_post_targets(branchname(), targets);
938 queueing_packet_writer qpw(app, targets);
939 packet_db_writer dbw(app);
940 cert_manifest_testresult(m, idx(args, 1)(), app, dbw);
941 cert_manifest_testresult(m, idx(args, 1)(), app, qpw);
942}
943
944CMD(approve, "certificate", "(file|manifest) ID1 ID2",
945 "approve of a manifest or file change")
946{
947 if (args.size() != 3)
948 throw usage(name);
949
950 if (idx(args, 0)() == "manifest")
951 {
952 manifest_id m1, m2;
953 complete(app, idx(args, 1)(), m1);
954 complete(app, idx(args, 2)(), m2);
955 set<url> targets;
956 cert_value branchname;
957 guess_branch (m1, app, branchname);
958 app.lua.hook_get_post_targets(branchname(), targets);
959 queueing_packet_writer qpw(app, targets);
960 packet_db_writer dbw(app);
961 cert_manifest_approval(m1, m2, true, app, dbw);
962 cert_manifest_approval(m1, m2, true, app, qpw);
963 }
964 else if (idx(args, 0)() == "file")
965 {
966 packet_db_writer dbw(app);
967 file_id f1, f2;
968 complete(app, idx(args, 1)(), f1);
969 complete(app, idx(args, 2)(), f2);
970 set<url> targets;
971 N(app.branch_name() != "", F("need --branch argument for posting"));
972 app.lua.hook_get_post_targets(cert_value(app.branch_name()), targets);
973 queueing_packet_writer qpw(app, targets);
974 cert_file_approval(f1, f2, true, app, dbw);
975 cert_file_approval(f1, f2, true, app, qpw);
976 }
977 else
978 throw usage(name);
979}
980
981CMD(disapprove, "certificate", "(file|manifest) ID1 ID2",
982 "disapprove of a manifest or file change")
983{
984 if (args.size() != 3)
985 throw usage(name);
986
987 if (idx(args, 0)() == "manifest")
988 {
989 manifest_id m1, m2;
990 complete(app, idx(args, 1)(), m1);
991 complete(app, idx(args, 2)(), m2);
992 set<url> targets;
993 cert_value branchname;
994 guess_branch(m1, app, branchname);
995 app.lua.hook_get_post_targets(branchname(), targets);
996 queueing_packet_writer qpw(app, targets);
997 packet_db_writer dbw(app);
998 cert_manifest_approval(m1, m2, false, app, dbw);
999 cert_manifest_approval(m1, m2, false, app, qpw);
1000 }
1001 else if (idx(args, 0)() == "file")
1002 {
1003 file_id f1, f2;
1004 complete(app, idx(args, 1)(), f1);
1005 complete(app, idx(args, 2)(), f2);
1006 set<url> targets;
1007 N(app.branch_name() != "", F("need --branch argument for posting"));
1008 app.lua.hook_get_post_targets(cert_value(app.branch_name()), targets);
1009 queueing_packet_writer qpw(app, targets);
1010 packet_db_writer dbw(app);
1011 cert_file_approval(f1, f2, false, app, dbw);
1012 cert_file_approval(f1, f2, false, app, qpw);
1013 }
1014 else
1015 throw usage(name);
1016}
1017
1018
1019CMD(comment, "certificate", "(file|manifest) ID [COMMENT]",
1020 "comment on a file or manifest version")
1021{
1022 if (args.size() != 2 && args.size() != 3)
1023 throw usage(name);
1024
1025 string comment;
1026 if (args.size() == 3)
1027 comment = idx(args, 2)();
1028 else
1029 N(app.lua.hook_edit_comment("", comment),
1030 F("edit comment failed"));
1031
1032 N(comment.find_first_not_of(" \r\t\n") != string::npos,
1033 F("empty comment"));
1034
1035 if (idx(args, 0)() == "file")
1036 {
1037 file_id f;
1038 complete(app, idx(args, 1)(), f);
1039 set<url> targets;
1040 N(app.branch_name() != "", F("need --branch argument for posting"));
1041 app.lua.hook_get_post_targets(cert_value(app.branch_name()), targets);
1042 queueing_packet_writer qpw(app, targets);
1043 packet_db_writer dbw(app);
1044 cert_file_comment(f, comment, app, dbw);
1045 cert_file_comment(f, comment, app, qpw);
1046 }
1047 else if (idx(args, 0)() == "manifest")
1048 {
1049 manifest_id m;
1050 complete(app, idx(args, 1)(), m);
1051 set<url> targets;
1052 cert_value branchname;
1053 guess_branch (m, app, branchname);
1054 app.lua.hook_get_post_targets(branchname(), targets);
1055 queueing_packet_writer qpw(app, targets);
1056 packet_db_writer dbw(app);
1057 cert_manifest_comment(m, comment, app, dbw);
1058 cert_manifest_comment(m, comment, app, qpw);
1059 }
1060 else
1061 throw usage(name);
1062}
1063
1064
1065CMD(add, "working copy", "PATHNAME...", "add files to working copy")
1066{
1067 if (args.size() < 1)
1068 throw usage(name);
1069
1070 manifest_map man;
1071 work_set work;
1072 get_manifest_map(man);
1073 get_work_set(work);
1074 bool rewrite_work = false;
1075
1076 for (vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
1077 build_addition(file_path((*i)()), app, work, man, rewrite_work);
1078
1079 if (rewrite_work)
1080 put_work_set(work);
1081
1082 update_any_attrs(app);
1083 app.write_options();
1084}
1085
1086CMD(drop, "working copy", "FILE...", "drop files from working copy")
1087{
1088 if (args.size() < 1)
1089 throw usage(name);
1090
1091 manifest_map man;
1092 work_set work;
1093 get_manifest_map(man);
1094 get_work_set(work);
1095 bool rewrite_work = false;
1096
1097 for (vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
1098 build_deletion(file_path((*i)()), app, work, man, rewrite_work);
1099
1100 if (rewrite_work)
1101 put_work_set(work);
1102
1103 update_any_attrs(app);
1104 app.write_options();
1105}
1106
1107
1108CMD(rename, "working copy", "SRC DST", "rename entries in the working copy")
1109{
1110 if (args.size() != 2)
1111 throw usage(name);
1112
1113 manifest_map man;
1114 work_set work;
1115
1116 get_manifest_map(man);
1117 get_work_set(work);
1118 bool rewrite_work = false;
1119
1120 build_rename(file_path(idx(args, 0)()), file_path(idx(args, 1)()), app, work,
1121 man, rewrite_work);
1122
1123 if (rewrite_work)
1124 put_work_set(work);
1125
1126 update_any_attrs(app);
1127 app.write_options();
1128}
1129
1130
1131CMD(commit, "working copy", "MESSAGE", "commit working copy to database")
1132{
1133 string log_message("");
1134 manifest_map m_old, m_new;
1135 patch_set ps;
1136
1137 rename_edge renames;
1138
1139 get_manifest_map(m_old);
1140 calculate_new_manifest_map(m_old, m_new, renames.mapping, app);
1141 manifest_id old_id, new_id;
1142 calculate_ident(m_old, old_id);
1143 calculate_ident(m_new, new_id);
1144 renames.parent = old_id;
1145 renames.child = new_id;
1146
1147 if (args.size() != 0 && args.size() != 1)
1148 throw usage(name);
1149
1150 cert_value branchname;
1151 guess_branch (old_id, app, branchname);
1152
1153 P(F("committing %s to branch %s\n") % new_id % branchname);
1154 app.set_branch(branchname());
1155
1156 manifests_to_patch_set(m_old, m_new, renames, app, ps);
1157
1158 // get log message
1159 if (args.size() == 1)
1160 log_message = idx(args, 0)();
1161 else
1162 get_log_message(ps, app, log_message);
1163
1164 N(log_message.find_first_not_of(" \r\t\n") != string::npos,
1165 F("empty log message"));
1166
1167 {
1168 transaction_guard guard(app.db);
1169
1170 // process manifest delta or new manifest
1171 if (app.db.manifest_version_exists(ps.m_new))
1172 {
1173L(F("skipping manifest %s, already in database\n") % ps.m_new);
1174 }
1175 else
1176 {
1177if (app.db.manifest_version_exists(ps.m_old))
1178 {
1179 L(F("inserting manifest delta %s -> %s\n") % ps.m_old % ps.m_new);
1180 manifest_data m_old_data, m_new_data;
1181 base64< gzip<delta> > del;
1182 diff(m_old, m_new, del);
1183 app.db.put_manifest_version(ps.m_old, ps.m_new, manifest_delta(del));
1184 }
1185else
1186 {
1187 L(F("inserting full manifest %s\n") % ps.m_new);
1188 manifest_data m_new_data;
1189 write_manifest_map(m_new, m_new_data);
1190 app.db.put_manifest(ps.m_new, m_new_data);
1191 }
1192 }
1193
1194 // process file deltas
1195 for (set<patch_delta>::const_iterator i = ps.f_deltas.begin();
1196 i != ps.f_deltas.end(); ++i)
1197 {
1198if (app.db.file_version_exists(i->id_new))
1199 {
1200 L(F("skipping file delta %s, already in database\n") % i->id_new);
1201 }
1202else
1203 {
1204 if (app.db.file_version_exists(i->id_old))
1205 {
1206L(F("inserting delta %s -> %s\n") % i->id_old % i->id_new);
1207file_data old_data;
1208base64< gzip<data> > new_data;
1209app.db.get_file_version(i->id_old, old_data);
1210read_localized_data(i->path, new_data, app.lua);
1211base64< gzip<delta> > del;
1212diff(old_data.inner(), new_data, del);
1213app.db.put_file_version(i->id_old, i->id_new, file_delta(del));
1214 }
1215 else
1216 {
1217L(F("inserting full version %s\n") % i->id_old);
1218base64< gzip<data> > new_data;
1219read_localized_data(i->path, new_data, app.lua);
1220// sanity check
1221hexenc<id> tid;
1222calculate_ident(new_data, tid);
1223I(tid == i->id_new.inner());
1224app.db.put_file(i->id_new, file_data(new_data));
1225 }
1226 }
1227 }
1228
1229 // process file adds
1230 for (set<patch_addition>::const_iterator i = ps.f_adds.begin();
1231 i != ps.f_adds.end(); ++i)
1232 {
1233if (app.db.file_version_exists(i->ident))
1234 {
1235 L(F("skipping file %s %s, already in database\n")
1236 % i->path % i->ident);
1237 }
1238else
1239 {
1240 // it's a new file
1241 L(F("inserting new file %s %s\n") % i->path % i->ident);
1242 base64< gzip<data> > new_data;
1243 read_localized_data(i->path, new_data, app.lua);
1244 app.db.put_file(i->ident, new_data);
1245 }
1246 }
1247
1248 packet_db_writer dbw(app);
1249
1250 if (! m_old.empty())
1251 cert_manifest_ancestor(ps.m_old, ps.m_new, app, dbw);
1252
1253 cert_manifest_in_branch(ps.m_new, branchname, app, dbw);
1254 cert_manifest_date_now(ps.m_new, app, dbw);
1255 cert_manifest_author_default(ps.m_new, app, dbw);
1256 cert_manifest_changelog(ps.m_new, log_message, app, dbw);
1257
1258 if (! (ps.f_moves.empty() && renames.mapping.empty()))
1259 {
1260for (set<patch_move>::const_iterator mv = ps.f_moves.begin();
1261 mv != ps.f_moves.end(); ++mv)
1262 {
1263 rename_set::const_iterator rn = renames.mapping.find(mv->path_old);
1264 if (rn != renames.mapping.end())
1265 I(rn->second == mv->path_new);
1266 else
1267 renames.mapping.insert(make_pair(mv->path_old, mv->path_new));
1268 }
1269renames.parent = ps.m_old;
1270renames.child = ps.m_new;
1271cert_manifest_rename(ps.m_new, renames, app, dbw);
1272 }
1273
1274 // commit done, now queue diff for sending
1275
1276 if (app.db.manifest_version_exists(ps.m_new))
1277 {
1278set<url> targets;
1279app.lua.hook_get_post_targets(branchname, targets);
1280
1281// make sure the appropriate edges get queued for the network.
1282for (set<url>::const_iterator targ = targets.begin();
1283 targ != targets.end(); ++targ)
1284 {
1285 queue_edge_for_target_ancestor (*targ, ps.m_new, m_new, app);
1286 }
1287
1288// throw in all available certs for good measure
1289queueing_packet_writer qpw(app, targets);
1290vector< manifest<cert> > certs;
1291app.db.get_manifest_certs(ps.m_new, certs);
1292for(vector< manifest<cert> >::const_iterator i = certs.begin();
1293 i != certs.end(); ++i)
1294 qpw.consume_manifest_cert(*i);
1295 }
1296
1297 guard.commit();
1298 }
1299 // small race condition here...
1300 remove_work_set();
1301 put_manifest_map(m_new);
1302 P(F("committed %s\n") % ps.m_new);
1303
1304 update_any_attrs(app);
1305 app.write_options();
1306}
1307
1308CMD(update, "working copy", "", "update working copy")
1309{
1310
1311 manifest_data m_chosen_data;
1312 manifest_map m_old, m_working, m_chosen, m_new;
1313 manifest_id m_old_id, m_chosen_id;
1314
1315 transaction_guard guard(app.db);
1316
1317 get_manifest_map(m_old);
1318 calculate_ident(m_old, m_old_id);
1319 calculate_new_manifest_map(m_old, m_working, app);
1320
1321 pick_update_target(m_old_id, app, m_chosen_id);
1322 if (m_old_id == m_chosen_id)
1323 {
1324 P(F("already up to date at %s\n") % m_old_id);
1325 return;
1326 }
1327 P(F("selected update target %s\n") % m_chosen_id);
1328 app.db.get_manifest_version(m_chosen_id, m_chosen_data);
1329 read_manifest_map(m_chosen_data, m_chosen);
1330
1331 rename_edge left_renames, right_renames;
1332 update_merge_provider merger(app);
1333 N(merge3(m_old, m_chosen, m_working, app, merger, m_new,
1334 left_renames.mapping, right_renames.mapping),
1335 F("manifest merge failed, no update performed"));
1336
1337 P(F("calculating patchset for update\n"));
1338 patch_set ps;
1339 manifests_to_patch_set(m_working, m_new, app, ps);
1340
1341 L(F("applying %d deletions to files in tree\n") % ps.f_dels.size());
1342 for (set<file_path>::const_iterator i = ps.f_dels.begin();
1343 i != ps.f_dels.end(); ++i)
1344 {
1345 L(F("deleting %s\n") % (*i));
1346 delete_file(*i);
1347 }
1348
1349 L(F("applying %d moves to files in tree\n") % ps.f_moves.size());
1350 for (set<patch_move>::const_iterator i = ps.f_moves.begin();
1351 i != ps.f_moves.end(); ++i)
1352 {
1353 L(F("moving %s -> %s\n") % i->path_old % i->path_new);
1354 make_dir_for(i->path_new);
1355 move_file(i->path_old, i->path_new);
1356 }
1357
1358 L(F("applying %d additions to tree\n") % ps.f_adds.size());
1359 for (set<patch_addition>::const_iterator i = ps.f_adds.begin();
1360 i != ps.f_adds.end(); ++i)
1361 {
1362 L(F("adding %s as %s\n") % i->ident % i->path);
1363 file_data tmp;
1364 if (app.db.file_version_exists(i->ident))
1365app.db.get_file_version(i->ident, tmp);
1366 else if (merger.temporary_store.find(i->ident) != merger.temporary_store.end())
1367tmp = merger.temporary_store[i->ident];
1368 else
1369I(false); // trip assert. this should be impossible.
1370 write_localized_data(i->path, tmp.inner(), app.lua);
1371 }
1372
1373 L(F("applying %d deltas to tree\n") % ps.f_deltas.size());
1374 for (set<patch_delta>::const_iterator i = ps.f_deltas.begin();
1375 i != ps.f_deltas.end(); ++i)
1376 {
1377 P(F("updating file %s: %s -> %s\n")
1378% i->path % i->id_old % i->id_new);
1379
1380 // sanity check
1381 {
1382base64< gzip<data> > dtmp;
1383hexenc<id> dtmp_id;
1384read_localized_data(i->path, dtmp, app.lua);
1385calculate_ident(dtmp, dtmp_id);
1386I(dtmp_id == i->id_old.inner());
1387 }
1388
1389 // ok, replace with new version
1390 {
1391file_data tmp;
1392if (app.db.file_version_exists(i->id_new))
1393 app.db.get_file_version(i->id_new, tmp);
1394else if (merger.temporary_store.find(i->id_new) != merger.temporary_store.end())
1395 tmp = merger.temporary_store[i->id_new];
1396else
1397 I(false); // trip assert. this should be impossible.
1398write_localized_data(i->path, tmp.inner(), app.lua);
1399 }
1400 }
1401
1402 L(F("update successful\n"));
1403 guard.commit();
1404
1405 // small race condition here...
1406 // nb: we write out m_chosen, not m_new, because the manifest-on-disk
1407 // is the basis of the working copy, not the working copy itself.
1408 put_manifest_map(m_chosen);
1409 P(F("updated to base version %s\n") % m_chosen_id);
1410
1411 update_any_attrs(app);
1412 app.write_options();
1413}
1414
1415CMD(revert, "working copy", "[FILE]...", "revert file(s) or entire working copy")
1416{
1417 manifest_map m_old;
1418
1419 if (args.size() == 0)
1420 {
1421 // revert the whole thing
1422 get_manifest_map(m_old);
1423 for (manifest_map::const_iterator i = m_old.begin(); i != m_old.end(); ++i)
1424{
1425 path_id_pair pip(*i);
1426
1427 N(app.db.file_version_exists(pip.ident()),
1428 F("no file version %s found in database for %s")
1429 % pip.ident() % pip.path());
1430
1431 file_data dat;
1432 L(F("writing file %s to %s\n") %
1433 pip.ident() % pip.path());
1434 app.db.get_file_version(pip.ident(), dat);
1435 write_localized_data(pip.path(), dat.inner(), app.lua);
1436}
1437 remove_work_set();
1438 }
1439 else
1440 {
1441 work_set work;
1442 get_manifest_map(m_old);
1443 get_work_set(work);
1444
1445 // revert some specific files
1446 vector<utf8> work_args (args.begin(), args.end());
1447 for (size_t i = 0; i < work_args.size(); ++i)
1448{
1449 string arg(idx(work_args, i)());
1450 if (directory_exists(arg))
1451 {
1452 // simplest is to just add all files from that
1453 // directory.
1454 string dir = arg;
1455 int off = dir.find_last_not_of('/');
1456 if (off != -1)
1457dir = dir.substr(0, off + 1);
1458 dir += '/';
1459 for (manifest_map::const_iterator i = m_old.begin();
1460 i != m_old.end(); ++i)
1461{
1462 file_path p = i->first;
1463 if (! p().compare(0, dir.length(), dir))
1464 work_args.push_back(p());
1465}
1466 continue;
1467 }
1468
1469 N((m_old.find(arg) != m_old.end()) ||
1470 (work.adds.find(arg) != work.adds.end()) ||
1471 (work.dels.find(arg) != work.dels.end()) ||
1472 (work.renames.find(arg) != work.renames.end()),
1473 F("nothing known about %s") % arg);
1474
1475 if (m_old.find(arg) != m_old.end())
1476 {
1477 path_id_pair pip(m_old.find(arg));
1478 L(F("reverting %s to %s\n") %
1479pip.path() % pip.ident());
1480
1481 N(app.db.file_version_exists(pip.ident()),
1482F("no file version %s found in database for %s")
1483% pip.ident() % pip.path());
1484
1485 file_data dat;
1486 L(F("writing file %s to %s\n") %
1487pip.ident() % pip.path());
1488 app.db.get_file_version(pip.ident(), dat);
1489 write_localized_data(pip.path(), dat.inner(), app.lua);
1490
1491 // a deleted file will always appear in the manifest
1492 if (work.dels.find(arg) != work.dels.end())
1493{
1494 L(F("also removing deletion for %s\n") % arg);
1495 work.dels.erase(arg);
1496}
1497 }
1498 else if (work.renames.find(arg) != work.renames.end())
1499 {
1500 L(F("removing rename for %s\n") % arg);
1501 work.renames.erase(arg);
1502 }
1503 else
1504 {
1505 I(work.adds.find(arg) != work.adds.end());
1506 L(F("removing addition for %s\n") % arg);
1507 work.adds.erase(arg);
1508 }
1509}
1510 // race
1511 put_work_set(work);
1512 }
1513
1514 update_any_attrs(app);
1515 app.write_options();
1516}
1517
1518
1519CMD(cat, "tree", "(file|manifest) ID", "write file or manifest from database to stdout")
1520{
1521 if (args.size() != 2)
1522 throw usage(name);
1523
1524 transaction_guard guard(app.db);
1525
1526 if (idx(args, 0)() == "file")
1527 {
1528 file_data dat;
1529 file_id ident;
1530 complete(app, idx(args, 1)(), ident);
1531
1532 N(app.db.file_version_exists(ident),
1533F("no file version %s found in database") % ident);
1534
1535 L(F("dumping file %s\n") % ident);
1536 app.db.get_file_version(ident, dat);
1537 data unpacked;
1538 unpack(dat.inner(), unpacked);
1539 cout.write(unpacked().data(), unpacked().size());
1540
1541 }
1542 else if (idx(args, 0)() == "manifest")
1543 {
1544 manifest_data dat;
1545 manifest_id ident;
1546 complete(app, idx(args, 1)(), ident);
1547
1548 N(app.db.manifest_version_exists(ident),
1549F("no manifest version %s found in database") % ident);
1550
1551 L(F("dumping manifest %s\n") % ident);
1552 app.db.get_manifest_version(ident, dat);
1553 data unpacked;
1554 unpack(dat.inner(), unpacked);
1555 cout.write(unpacked().data(), unpacked().size());
1556 }
1557 else
1558 throw usage(name);
1559
1560 guard.commit();
1561}
1562
1563
1564CMD(checkout, "tree", "MANIFEST-ID DIRECTORY\nDIRECTORY", "check out tree state from database into directory")
1565{
1566
1567 manifest_id ident;
1568 string dir;
1569
1570 if (args.size() != 1 && args.size() != 2)
1571 throw usage(name);
1572
1573 if (args.size() == 1)
1574 {
1575 set<manifest_id> heads;
1576 N(app.branch_name() != "", F("need --branch argument for branch-based checkout"));
1577 get_branch_heads(app.branch_name(), app, heads);
1578 N(heads.size() > 0, F("branch %s is empty") % app.branch_name);
1579 N(heads.size() == 1, F("branch %s has multiple heads") % app.branch_name);
1580 ident = *(heads.begin());
1581 dir = idx(args, 0)();
1582 }
1583
1584 else
1585 {
1586 complete(app, idx(args, 0)(), ident);
1587 dir = idx(args, 1)();
1588 }
1589
1590 if (dir != string("."))
1591 {
1592 local_path lp(dir);
1593 mkdir_p(lp);
1594 chdir(dir.c_str());
1595 }
1596
1597 transaction_guard guard(app.db);
1598
1599 file_data data;
1600 manifest_map m;
1601
1602 N(app.db.manifest_version_exists(ident),
1603 F("no manifest version %s found in database") % ident);
1604
1605 L(F("checking out manifest %s to directory %s\n") % ident % dir);
1606 manifest_data m_data;
1607 app.db.get_manifest_version(ident, m_data);
1608 read_manifest_map(m_data, m);
1609 put_manifest_map(m);
1610
1611 for (manifest_map::const_iterator i = m.begin(); i != m.end(); ++i)
1612 {
1613 path_id_pair pip(*i);
1614
1615 N(app.db.file_version_exists(pip.ident()),
1616F("no file version %s found in database for %s")
1617% pip.ident() % pip.path());
1618
1619 file_data dat;
1620 L(F("writing file %s to %s\n") %
1621pip.ident() % pip.path());
1622 app.db.get_file_version(pip.ident(), dat);
1623 write_localized_data(pip.path(), dat.inner(), app.lua);
1624 }
1625 remove_work_set();
1626 guard.commit();
1627 update_any_attrs(app);
1628 app.write_options();
1629}
1630
1631ALIAS(co, checkout, "tree", "MANIFEST-ID DIRECTORY\nDIRECTORY",
1632 "check out tree state from database; alias for checkout")
1633
1634CMD(heads, "tree", "", "show unmerged heads of branch")
1635{
1636 set<manifest_id> heads;
1637 if (args.size() != 0)
1638 throw usage(name);
1639
1640 if (app.branch_name() == "")
1641 {
1642 cout << "please specify a branch, with --branch=BRANCH" << endl;
1643 return;
1644 }
1645
1646 get_branch_heads(app.branch_name(), app, heads);
1647
1648 if (heads.size() == 0)
1649 cout << "branch '" << app.branch_name << "' is empty" << endl;
1650 else if (heads.size() == 1)
1651 cout << "branch '" << app.branch_name << "' is currently merged:" << endl;
1652 else
1653 cout << "branch '" << app.branch_name << "' is currently unmerged:" << endl;
1654
1655 for (set<manifest_id>::const_iterator i = heads.begin();
1656 i != heads.end(); ++i)
1657 {
1658 cout << i->inner()() << endl;
1659 }
1660}
1661
1662
1663CMD(merge, "tree", "", "merge unmerged heads of branch")
1664{
1665 set<manifest_id> heads;
1666
1667 if (args.size() != 0)
1668 throw usage(name);
1669
1670 if (app.branch_name() == "")
1671 {
1672 cout << "please specify a branch, with --branch=BRANCH" << endl;
1673 return;
1674 }
1675
1676 get_branch_heads(app.branch_name(), app, heads);
1677
1678 if (heads.size() == 0)
1679 {
1680 cout << "branch '" << app.branch_name << "' is empty" << endl;
1681 return;
1682 }
1683 else if (heads.size() == 1)
1684 {
1685 cout << "branch '" << app.branch_name << "' is merged" << endl;
1686 return;
1687 }
1688 else
1689 {
1690 set<url> targets;
1691 app.lua.hook_get_post_targets(app.branch_name(), targets);
1692
1693 set<manifest_id>::const_iterator i = heads.begin();
1694 manifest_id left = *i;
1695 manifest_id ancestor;
1696 size_t count = 1;
1697 for (++i; i != heads.end(); ++i, ++count)
1698{
1699 manifest_id right = *i;
1700 P(F("merging with manifest %d / %d: %s <-> %s\n")
1701 % count % heads.size() % left % right);
1702
1703 manifest_id merged;
1704 transaction_guard guard(app.db);
1705 try_one_merge (left, right, merged, app, targets);
1706
1707 // merged 1 edge; now we commit this, update merge source and
1708 // try next one
1709
1710 packet_db_writer dbw(app);
1711 queueing_packet_writer qpw(app, targets);
1712 cert_manifest_in_branch(merged, app.branch_name(), app, dbw);
1713 cert_manifest_in_branch(merged, app.branch_name(), app, qpw);
1714
1715 string log = (F("merge of %s and %s\n") % left % right).str();
1716 cert_manifest_changelog(merged, log, app, dbw);
1717 cert_manifest_changelog(merged, log, app, qpw);
1718
1719 guard.commit();
1720 P(F("[source] %s\n") % left);
1721 P(F("[source] %s\n") % right);
1722 P(F("[merged] %s\n") % merged);
1723 left = merged;
1724}
1725 }
1726
1727 app.write_options();
1728}
1729
1730
1731// float and fmerge are simple commands for debugging the line merger.
1732// most of the time, leave them commented out. they can be helpful for certain
1733// cases, though.
1734
1735 CMD(fload, "tree", "", "load file contents into db")
1736 {
1737 string s = get_stdin();
1738 base64< gzip< data > > gzd;
1739
1740 pack(data(s), gzd);
1741
1742 file_id f_id;
1743 file_data f_data(gzd);
1744
1745 calculate_ident (f_data, f_id);
1746
1747 packet_db_writer dbw(app);
1748 dbw.consume_file_data(f_id, f_data);
1749 }
1750
1751 CMD(fmerge, "tree", "<parent> <left> <right>", "merge 3 files and output result")
1752 {
1753 if (args.size() != 3)
1754 throw usage(name);
1755
1756 file_id anc_id(idx(args, 0)()), left_id(idx(args, 1)()), right_id(idx(args, 2)());
1757 file_data anc, left, right;
1758 data anc_unpacked, left_unpacked, right_unpacked;
1759
1760 N(app.db.file_version_exists (anc_id),
1761 F("ancestor file id does not exist"));
1762
1763 N(app.db.file_version_exists (left_id),
1764 F("left file id does not exist"));
1765
1766 N(app.db.file_version_exists (right_id),
1767 F("right file id does not exist"));
1768
1769 app.db.get_file_version(anc_id, anc);
1770 app.db.get_file_version(left_id, left);
1771 app.db.get_file_version(right_id, right);
1772
1773 unpack(left.inner(), left_unpacked);
1774 unpack(anc.inner(), anc_unpacked);
1775 unpack(right.inner(), right_unpacked);
1776
1777 vector<string> anc_lines, left_lines, right_lines, merged_lines;
1778
1779 split_into_lines(anc_unpacked(), anc_lines);
1780 split_into_lines(left_unpacked(), left_lines);
1781 split_into_lines(right_unpacked(), right_lines);
1782 N(merge3(anc_lines, left_lines, right_lines, merged_lines), F("merge failed"));
1783 copy(merged_lines.begin(), merged_lines.end(), ostream_iterator<string>(cout, "\n"));
1784
1785 }
1786
1787CMD(propagate, "tree", "SOURCE-BRANCH DEST-BRANCH",
1788 "merge from one branch to another asymmetrically")
1789{
1790 /*
1791
1792 this is a special merge operator, but very useful for people maintaining
1793 "slightly disparate but related" trees. it does a one-way merge; less
1794 powerful than putting things in the same branch and also more flexible.
1795
1796 1. check to see if src and dst branches are merged, if not abort, if so
1797 call heads N1 and N2 respectively.
1798
1799 2. (FIXME: not yet present) run the hook propagate ("src-branch",
1800 "dst-branch", N1, N2) which gives the user a chance to massage N1 into
1801 a state which is likely to "merge nicely" with N2, eg. edit pathnames,
1802 omit optional files of no interest.
1803
1804 3. do a normal 2 or 3-way merge on N1 and N2, depending on the
1805 existence of common ancestors.
1806
1807 4. save the results as the delta (N2,M), the ancestry edges (N1,M)
1808 and (N2,M), and the cert (N2,dst).
1809
1810 5. queue the resulting packets to send to the url for dst-branch, not
1811 src-branch.
1812
1813 */
1814
1815 set<manifest_id> src_heads, dst_heads;
1816
1817 if (args.size() != 2)
1818 throw usage(name);
1819
1820 get_branch_heads(idx(args, 0)(), app, src_heads);
1821 get_branch_heads(idx(args, 1)(), app, dst_heads);
1822
1823 if (src_heads.size() == 0)
1824 {
1825 P(F("branch '%s' is empty\n") % idx(args, 0)());
1826 return;
1827 }
1828 else if (src_heads.size() != 1)
1829 {
1830 P(F("branch '%s' is not merged\n") % idx(args, 0)());
1831 return;
1832 }
1833 else if (dst_heads.size() == 0)
1834 {
1835 P(F("branch '%s' is empty\n") % idx(args, 1)());
1836 return;
1837 }
1838 else if (dst_heads.size() != 1)
1839 {
1840 P(F("branch '%s' is not merged\n") % idx(args, 1)());
1841 return;
1842 }
1843 else
1844 {
1845 set<url> targets;
1846 app.lua.hook_get_post_targets(idx(args, 1)(), targets);
1847
1848 set<manifest_id>::const_iterator src_i = src_heads.begin();
1849 set<manifest_id>::const_iterator dst_i = dst_heads.begin();
1850
1851 manifest_id merged;
1852 transaction_guard guard(app.db);
1853 try_one_merge (*src_i, *dst_i, merged, app, targets);
1854
1855 packet_db_writer dbw(app);
1856 queueing_packet_writer qpw(app, targets);
1857
1858 cert_manifest_in_branch(merged, idx(args, 1)(), app, dbw);
1859 cert_manifest_in_branch(merged, idx(args, 1)(), app, qpw);
1860
1861 string log = (F("propagate of %s and %s from branch '%s' to '%s'\n")
1862 % (*src_i) % (*dst_i) % idx(args,0) % idx(args,1)).str();
1863
1864 cert_manifest_changelog(merged, log, app, qpw);
1865 cert_manifest_changelog(merged, log, app, dbw);
1866
1867 guard.commit();
1868 }
1869}
1870
1871
1872CMD(complete, "informative", "(manifest|file) PARTIAL-ID", "complete partial id")
1873{
1874 if (args.size() != 2)
1875 throw usage(name);
1876
1877 if (idx(args, 0)() == "manifest")
1878 {
1879 N(idx(args, 1)().find_first_not_of("abcdef0123456789") == string::npos,
1880F("non-hex digits in partial id"));
1881 set<manifest_id> completions;
1882 app.db.complete(idx(args, 1)(), completions);
1883 for (set<manifest_id>::const_iterator i = completions.begin();
1884 i != completions.end(); ++i)
1885cout << i->inner()() << endl;
1886 }
1887 else if (idx(args, 0)() == "file")
1888 {
1889 N(idx(args, 1)().find_first_not_of("abcdef0123456789") == string::npos,
1890F("non-hex digits in partial id"));
1891 set<file_id> completions;
1892 app.db.complete(idx(args, 1)(), completions);
1893 for (set<file_id>::const_iterator i = completions.begin();
1894 i != completions.end(); ++i)
1895cout << i->inner()() << endl;
1896 }
1897 else
1898 throw usage(name);
1899}
1900
1901CMD(diff, "informative", "[MANIFEST-ID [MANIFEST-ID]]", "show current diffs on stdout")
1902{
1903 manifest_map m_old, m_new;
1904 patch_set ps;
1905 bool new_is_archived;
1906
1907 transaction_guard guard(app.db);
1908
1909 if (args.size() == 0)
1910 {
1911 get_manifest_map(m_old);
1912 calculate_new_manifest_map(m_old, m_new, app);
1913 new_is_archived = false;
1914 }
1915 else if (args.size() == 1)
1916 {
1917 manifest_id m_old_id;
1918 complete(app, idx(args, 0)(), m_old_id);
1919 manifest_data m_old_data;
1920 app.db.get_manifest_version(m_old_id, m_old_data);
1921 read_manifest_map(m_old_data, m_old);
1922
1923 manifest_map parent;
1924 get_manifest_map(parent);
1925 calculate_new_manifest_map(parent, m_new, app);
1926 new_is_archived = false;
1927 }
1928 else if (args.size() == 2)
1929 {
1930 manifest_id m_old_id, m_new_id;
1931
1932 complete(app, idx(args, 0)(), m_old_id);
1933 complete(app, idx(args, 1)(), m_new_id);
1934
1935 manifest_data m_old_data, m_new_data;
1936 app.db.get_manifest_version(m_old_id, m_old_data);
1937 app.db.get_manifest_version(m_new_id, m_new_data);
1938
1939 read_manifest_map(m_old_data, m_old);
1940 read_manifest_map(m_new_data, m_new);
1941 new_is_archived = true;
1942 }
1943 else
1944 {
1945 throw usage(name);
1946 }
1947
1948 manifests_to_patch_set(m_old, m_new, app, ps);
1949
1950 stringstream summary;
1951 patch_set_to_text_summary(ps, summary);
1952 vector<string> lines;
1953 split_into_lines(summary.str(), lines);
1954 for (vector<string>::iterator i = lines.begin(); i != lines.end(); ++i)
1955 cout << "# " << *i << endl;
1956
1957 for (set<patch_delta>::const_iterator i = ps.f_deltas.begin();
1958 i != ps.f_deltas.end(); ++i)
1959 {
1960 file_data f_old;
1961 gzip<data> decoded_old;
1962 data decompressed_old, decompressed_new;
1963 vector<string> old_lines, new_lines;
1964
1965 app.db.get_file_version(i->id_old, f_old);
1966 decode_base64(f_old.inner(), decoded_old);
1967 decode_gzip(decoded_old, decompressed_old);
1968
1969 if (new_is_archived)
1970 {
1971 file_data f_new;
1972 gzip<data> decoded_new;
1973 app.db.get_file_version(i->id_new, f_new);
1974 decode_base64(f_new.inner(), decoded_new);
1975 decode_gzip(decoded_new, decompressed_new);
1976 }
1977 else
1978 {
1979 read_localized_data(i->path, decompressed_new, app.lua);
1980 }
1981
1982 split_into_lines(decompressed_old(), old_lines);
1983 split_into_lines(decompressed_new(), new_lines);
1984
1985 unidiff(i->path(), i->path(), old_lines, new_lines, cout);
1986 }
1987 guard.commit();
1988}
1989
1990CMD(log, "informative", "[ID]", "print log history in reverse order")
1991{
1992 manifest_map m;
1993 manifest_id m_id;
1994 set<manifest_id> frontier, cycles;
1995
1996 if (args.size() > 1)
1997 throw usage(name);
1998
1999 if (args.size() == 1)
2000 {
2001 complete(app, idx(args, 0)(), m_id);
2002 }
2003 else
2004 {
2005 get_manifest_map(m);
2006 calculate_ident (m, m_id);
2007 }
2008
2009 frontier.insert(m_id);
2010
2011 cert_name ancestor_name(ancestor_cert_name);
2012 cert_name author_name(author_cert_name);
2013 cert_name date_name(date_cert_name);
2014 cert_name changelog_name(changelog_cert_name);
2015 cert_name comment_name(comment_cert_name);
2016
2017 set<file_id> no_comments;
2018
2019 while(! frontier.empty())
2020 {
2021 set<manifest_id> next_frontier;
2022 for (set<manifest_id>::const_iterator i = frontier.begin();
2023 i != frontier.end(); ++i)
2024{
2025 cout << "-----------------------------------------------------------------"
2026 << endl;
2027 cout << "Version: " << *i << endl;
2028
2029 cout << "Author:";
2030 vector< manifest<cert> > tmp;
2031 app.db.get_manifest_certs(*i, author_name, tmp);
2032 erase_bogus_certs(tmp, app);
2033 for (vector< manifest<cert> >::const_iterator j = tmp.begin();
2034 j != tmp.end(); ++j)
2035 {
2036 cert_value tv;
2037 decode_base64(j->inner().value, tv);
2038 cout << " " << tv;
2039 }
2040 cout << endl;
2041
2042 cout << "Date:";
2043 app.db.get_manifest_certs(*i, date_name, tmp);
2044 erase_bogus_certs(tmp, app);
2045 for (vector< manifest<cert> >::const_iterator j = tmp.begin();
2046 j != tmp.end(); ++j)
2047 {
2048 cert_value tv;
2049 decode_base64(j->inner().value, tv);
2050 cout << " " << tv;
2051 }
2052 cout << endl;
2053
2054 cout << "ChangeLog:" << endl << endl;
2055 app.db.get_manifest_certs(*i, changelog_name, tmp);
2056 erase_bogus_certs(tmp, app);
2057 for (vector< manifest<cert> >::const_iterator j = tmp.begin();
2058 j != tmp.end(); ++j)
2059 {
2060 cert_value tv;
2061 decode_base64(j->inner().value, tv);
2062 cout << " " << tv << endl;
2063 }
2064 cout << endl;
2065
2066 app.db.get_manifest_certs(*i, comment_name, tmp);
2067 erase_bogus_certs(tmp, app);
2068 if (!tmp.empty())
2069 {
2070 cout << "Manifest Comments:" << endl << endl;
2071 for (vector< manifest<cert> >::const_iterator j = tmp.begin();
2072 j != tmp.end(); ++j)
2073{
2074 cert_value tv;
2075 decode_base64(j->inner().value, tv);
2076 cout << j->inner().key << ": " << tv << endl;
2077}
2078 cout << endl;
2079 }
2080
2081 // pull any file-specific comments
2082 if (app.db.manifest_version_exists(*i))
2083 {
2084 manifest_data mdata;
2085 manifest_map mtmp;
2086 app.db.get_manifest_version(*i, mdata);
2087 read_manifest_map(mdata, mtmp);
2088 bool wrote_headline = false;
2089 for (manifest_map::const_iterator mi = mtmp.begin();
2090 mi != mtmp.end(); ++mi)
2091{
2092 path_id_pair pip(mi);
2093 if (no_comments.find(pip.ident()) != no_comments.end())
2094 continue;
2095
2096 vector< file<cert> > ftmp;
2097 app.db.get_file_certs(pip.ident(), comment_name, ftmp);
2098 erase_bogus_certs(ftmp, app);
2099 if (!ftmp.empty())
2100 {
2101 if (!wrote_headline)
2102{
2103 cout << "File Comments:" << endl << endl;
2104 wrote_headline = true;
2105}
2106
2107 cout << " " << pip.path() << endl;
2108 for (vector< file<cert> >::const_iterator j = ftmp.begin();
2109 j != ftmp.end(); ++j)
2110{
2111 cert_value tv;
2112 decode_base64(j->inner().value, tv);
2113 cout << " " << j->inner().key << ": " << tv << endl;
2114}
2115 }
2116 else
2117 no_comments.insert(pip.ident());
2118}
2119 if (wrote_headline)
2120cout << endl;
2121 }
2122
2123 app.db.get_manifest_certs(*i, ancestor_name, tmp);
2124 erase_bogus_certs(tmp, app);
2125 for (vector< manifest<cert> >::const_iterator j = tmp.begin();
2126 j != tmp.end(); ++j)
2127 {
2128 cert_value tv;
2129 decode_base64(j->inner().value, tv);
2130 manifest_id id(tv());
2131 if (cycles.find(id) == cycles.end())
2132{
2133 next_frontier.insert(id);
2134 cycles.insert(id);
2135}
2136 }
2137}
2138 frontier = next_frontier;
2139 }
2140}
2141
2142CMD(status, "informative", "", "show status of working copy")
2143{
2144 manifest_map m_old, m_new;
2145 manifest_id old_id, new_id;
2146 patch_set ps1;
2147 rename_edge renames;
2148
2149 N(bookdir_exists(),
2150 F("no monotone book-keeping directory '%s' found")
2151 % book_keeping_dir);
2152
2153 transaction_guard guard(app.db);
2154 get_manifest_map(m_old);
2155 calculate_ident(m_old, old_id);
2156 calculate_new_manifest_map(m_old, m_new, renames.mapping, app);
2157 calculate_ident(m_new, new_id);
2158
2159 renames.parent = old_id;
2160 renames.child = new_id;
2161 manifests_to_patch_set(m_old, m_new, renames, app, ps1);
2162 patch_set_to_text_summary(ps1, cout);
2163
2164 guard.commit();
2165}
2166
2167static void ls_branches (string name, app_state & app, vector<utf8> const & args)
2168{
2169 transaction_guard guard(app.db);
2170 vector< manifest<cert> > certs;
2171 app.db.get_manifest_certs(branch_cert_name, certs);
2172
2173 vector<string> names;
2174 for (size_t i = 0; i < certs.size(); ++i)
2175 {
2176 cert_value name;
2177 decode_base64(idx(certs, i).inner().value, name);
2178 names.push_back(name());
2179 }
2180
2181 sort(names.begin(), names.end());
2182 names.erase(std::unique(names.begin(), names.end()), names.end());
2183 for (size_t i = 0; i < names.size(); ++i)
2184 cout << idx(names, i) << endl;
2185
2186 guard.commit();
2187}
2188
2189
2190struct unknown_itemizer : public tree_walker
2191{
2192 app_state & app;
2193 manifest_map & man;
2194 bool want_ignored;
2195 unknown_itemizer(app_state & a, manifest_map & m, bool i)
2196 : app(a), man(m), want_ignored(i) {}
2197 virtual void visit_file(file_path const & path)
2198 {
2199 if (man.find(path) == man.end())
2200 {
2201 if (want_ignored)
2202{
2203 if (app.lua.hook_ignore_file(path))
2204 cout << path() << endl;
2205}
2206 else
2207{
2208 if (!app.lua.hook_ignore_file(path))
2209 cout << path() << endl;
2210}
2211 }
2212 }
2213};
2214
2215
2216static void ls_unknown (app_state & app, bool want_ignored)
2217{
2218 manifest_map m_old, m_new;
2219 get_manifest_map(m_old);
2220 calculate_new_manifest_map(m_old, m_new, app);
2221 unknown_itemizer u(app, m_new, want_ignored);
2222 walk_tree(u);
2223}
2224
2225static void ls_queue (string name, app_state & app)
2226{
2227 set<url> target_set;
2228 app.db.get_queued_targets(target_set);
2229 vector<url> targets;
2230 copy(target_set.begin(), target_set.end(), back_inserter(targets));
2231
2232 for (size_t i = 0; i < targets.size(); ++i)
2233 {
2234 size_t queue_count;
2235 string content;
2236 cout << "target " << i << ": "
2237 << idx(targets, i) << endl;
2238 app.db.get_queue_count(idx(targets, i), queue_count);
2239 for (size_t j = 0; j < queue_count; ++j)
2240{
2241 app.db.get_queued_content(idx(targets, i), j, content);
2242 cout << " target " << i << ", packet " << j
2243 << ": " << content.size() << " bytes" << endl;
2244}
2245 }
2246}
2247
2248CMD(reindex, "network", "COLLECTION...",
2249 "rebuild the hash-tree indices used to sync COLLECTION over the network")
2250{
2251 if (args.size() < 1)
2252 throw usage(name);
2253
2254 transaction_guard guard(app.db);
2255 ui.set_tick_trailer("rehashing db");
2256 app.db.rehash();
2257 for (size_t i = 0; i < args.size(); ++i)
2258 {
2259 ui.set_tick_trailer(string("rebuilding hash-tree indices for ") + idx(args,i)());
2260 rebuild_merkle_trees(app, idx(args,i));
2261 }
2262 guard.commit();
2263}
2264
2265CMD(push, "network", "ADDRESS[:PORTNUMBER] COLLECTION...",
2266 "alias for 'netsync client readonly'")
2267{
2268 if (args.size() < 2)
2269 throw usage(name);
2270
2271 rsa_keypair_id key;
2272 N(guess_default_key(key, app), "could not guess default signing key");
2273 app.signing_key = key;
2274
2275 utf8 addr(idx(args,0));
2276 vector<utf8> collections(args.begin() + 1, args.end());
2277 run_netsync_protocol(client_voice, source_role, addr, collections, app);
2278}
2279
2280CMD(pull, "network", "ADDRESS[:PORTNUMBER] COLLECTION...",
2281 "alias for 'netsync client writeonly'")
2282{
2283 if (args.size() < 2)
2284 throw usage(name);
2285
2286 if (app.signing_key() == "")
2287 W(F("doing anonymous pull\n"));
2288
2289 utf8 addr(idx(args,0));
2290 vector<utf8> collections(args.begin() + 1, args.end());
2291 run_netsync_protocol(client_voice, sink_role, addr, collections, app);
2292}
2293
2294CMD(sync, "network", "ADDRESS[:PORTNUMBER] COLLECTION...",
2295 "alias for 'netsync client readwrite'")
2296{
2297 if (args.size() < 2)
2298 throw usage(name);
2299
2300 rsa_keypair_id key;
2301 N(guess_default_key(key, app), "could not guess default signing key");
2302 app.signing_key = key;
2303
2304 utf8 addr(idx(args,0));
2305 vector<utf8> collections(args.begin() + 1, args.end());
2306 run_netsync_protocol(client_voice, source_and_sink_role, addr, collections, app);
2307}
2308
2309CMD(serve, "network", "ADDRESS[:PORTNUMBER] COLLECTION...",
2310 "alias for 'netsync server readwrite'")
2311{
2312 if (args.size() < 2)
2313 throw usage(name);
2314
2315 rsa_keypair_id key;
2316 N(guess_default_key(key, app), "could not guess default signing key");
2317 app.signing_key = key;
2318
2319 utf8 addr(idx(args,0));
2320 vector<utf8> collections(args.begin() + 1, args.end());
2321 run_netsync_protocol(server_voice, source_and_sink_role, addr, collections, app);
2322}
2323
2324CMD(netsync, "network", "(client|server) (readonly|readwrite|writeonly) ADDRESS[:PORTNUMBER] COLLECTION...",
2325 "run synchronization for a given set of collections")
2326{
2327 if (args.size() < 4)
2328 throw usage(name);
2329
2330 protocol_voice voice = client_voice;
2331 protocol_role role = source_role;
2332
2333 if (idx(args,0)() == "client")
2334 voice = client_voice;
2335 else if (idx(args,0)() == "server")
2336 voice = server_voice;
2337 else
2338 throw usage(name);
2339
2340 if (idx(args,1)() == "readonly")
2341 role = source_role;
2342 else if (idx(args,1)() == "readwrite")
2343 role = source_and_sink_role;
2344 else if (idx(args,1)() == "writeonly")
2345 role = sink_role;
2346 else throw usage(name);
2347
2348 rsa_keypair_id key;
2349 N(guess_default_key(key, app), "could not guess default signing key");
2350 app.signing_key = key;
2351
2352 utf8 addr(idx(args,2));
2353 vector<utf8> collections(args.begin() + 3, args.end());
2354 run_netsync_protocol(voice, role, addr, collections, app);
2355}
2356
2357
2358
2359CMD(queue, "network", "list\nprint TARGET PACKET\ndelete TARGET PACKET\nadd URL\naddtree URL [ID...]",
2360 "list, print, delete, or add items to network queue")
2361{
2362 if (args.size() == 0)
2363 throw usage(name);
2364
2365 if (idx(args, 0)() == "list")
2366 ls_queue(name, app);
2367
2368 else if (idx(args, 0)() == "print"
2369 || idx(args, 0)() == "delete")
2370 {
2371 if (args.size() != 3)
2372throw usage(name);
2373 size_t target = boost::lexical_cast<size_t>(idx(args,1));
2374 size_t packet = boost::lexical_cast<size_t>(idx(args,2));
2375
2376 set<url> target_set;
2377 app.db.get_queued_targets(target_set);
2378 vector<url> targets;
2379 copy(target_set.begin(), target_set.end(), back_inserter(targets));
2380 N(target < targets.size(),
2381F("target number %d out of range") % target);
2382
2383 size_t queue_count;
2384 app.db.get_queue_count(idx(targets, target), queue_count);
2385
2386 N(packet < queue_count,
2387F("packet number %d out of range for target %d")
2388% packet % target);
2389
2390 string content;
2391 app.db.get_queued_content(idx(targets, target), packet, content);
2392
2393 if (idx(args, 0)() == "print")
2394{
2395 cout << content;
2396}
2397 else
2398{
2399 ui.inform(F("deleting %d byte posting for %s\n")
2400 % content.size() % idx(targets, target));
2401 app.db.delete_posting(idx(targets, target), packet);
2402}
2403 }
2404
2405 else if (idx(args, 0)() == "add")
2406 {
2407 if (args.size() != 2)
2408throw usage(name);
2409 url u;
2410 internalize_url(idx(args,1), u);
2411 string s = get_stdin();
2412 ui.inform(F("queueing %d bytes for %s\n") % s.size() % u);
2413 app.db.queue_posting(u, s);
2414 }
2415
2416 else if (idx(args, 0)() == "addtree")
2417 {
2418 if (args.size() < 2)
2419throw usage(name);
2420
2421 url u;
2422 internalize_url(idx(args,1), u);
2423 set<manifest_id> roots;
2424
2425 if (args.size() == 2)
2426{
2427 N(app.branch_name() != "", F("need --branch argument for addtree"));
2428 get_branch_heads(app.branch_name(), app, roots);
2429}
2430 else
2431{
2432 for (size_t i = 2; i < args.size(); ++i)
2433 {
2434 roots.insert(manifest_id(idx(args,i)()));
2435 }
2436}
2437
2438 set<url> targets;
2439 targets.insert (u);
2440 queueing_packet_writer qpw(app, targets);
2441
2442 transaction_guard guard(app.db);
2443
2444 for (set<manifest_id>::const_iterator i = roots.begin();
2445 i != roots.end(); ++i)
2446{
2447 set<manifest_id> ancs;
2448 find_oldest_ancestors (*i, ancs, app);
2449 for (set<manifest_id>::const_iterator j = ancs.begin();
2450 j != ancs.end(); ++j)
2451 {
2452 manifest_map empty, mm;
2453 manifest_data dat;
2454 patch_set ps;
2455
2456 // queue the ancestral state
2457 app.db.get_manifest_version(*j, dat);
2458 read_manifest_map(dat, mm);
2459 manifests_to_patch_set(empty, mm, app, ps);
2460 patch_set_to_packets(ps, app, qpw);
2461
2462 // queue everything between here and there
2463 reverse_queue rq(app.db);
2464 qpw.rev.reset(rq);
2465 write_ancestry_paths(*j, *i, app, qpw);
2466 qpw.rev.reset();
2467 }
2468}
2469 guard.commit();
2470 }
2471 else
2472 throw usage(name);
2473}
2474
2475CMD(list, "informative",
2476 "certs (file|manifest) ID\n"
2477 "keys [PATTERN]\n"
2478 "queue\n"
2479 "branches\n"
2480 "unknown\n"
2481 "ignored",
2482 "show certs, keys, branches, unknown or intentionally ignored files")
2483{
2484 if (args.size() == 0)
2485 throw usage(name);
2486
2487 vector<utf8>::const_iterator i = args.begin();
2488 ++i;
2489 vector<utf8> removed (i, args.end());
2490 if (idx(args, 0)() == "certs")
2491 ls_certs(name, app, removed);
2492 else if (idx(args, 0)() == "keys")
2493 ls_keys(name, app, removed);
2494 else if (idx(args, 0)() == "queue")
2495 ls_queue(name, app);
2496 else if (idx(args, 0)() == "branches")
2497 ls_branches(name, app, removed);
2498 else if (idx(args, 0)() == "unknown")
2499 ls_unknown(app, false);
2500 else if (idx(args, 0)() == "ignored")
2501 ls_unknown(app, true);
2502 else
2503 throw usage(name);
2504}
2505
2506ALIAS(ls, list, "informative",
2507 "certs (file|manifest) ID\n"
2508 "keys [PATTERN]\n"
2509 "branches\n"
2510 "unknown\n"
2511 "ignored", "show certs, keys, or branches")
2512
2513
2514 CMD(mdelta, "packet i/o", "OLDID NEWID", "write manifest delta packet to stdout")
2515{
2516 if (args.size() != 2)
2517 throw usage(name);
2518
2519 transaction_guard guard(app.db);
2520 packet_writer pw(cout);
2521
2522 manifest_id m_old_id, m_new_id;
2523 manifest_data m_old_data, m_new_data;
2524 manifest_map m_old, m_new;
2525 patch_set ps;
2526
2527 complete(app, idx(args, 0)(), m_old_id);
2528 complete(app, idx(args, 1)(), m_new_id);
2529
2530 app.db.get_manifest_version(m_old_id, m_old_data);
2531 app.db.get_manifest_version(m_new_id, m_new_data);
2532 read_manifest_map(m_old_data, m_old);
2533 read_manifest_map(m_new_data, m_new);
2534 manifests_to_patch_set(m_old, m_new, app, ps);
2535 patch_set_to_packets(ps, app, pw);
2536 guard.commit();
2537}
2538
2539CMD(fdelta, "packet i/o", "OLDID NEWID", "write file delta packet to stdout")
2540{
2541 if (args.size() != 2)
2542 throw usage(name);
2543
2544 transaction_guard guard(app.db);
2545 packet_writer pw(cout);
2546
2547 file_id f_old_id, f_new_id;
2548 file_data f_old_data, f_new_data;
2549
2550 complete(app, idx(args, 0)(), f_old_id);
2551 complete(app, idx(args, 1)(), f_new_id);
2552
2553 app.db.get_file_version(f_old_id, f_old_data);
2554 app.db.get_file_version(f_new_id, f_new_data);
2555 base64< gzip<delta> > del;
2556 diff(f_old_data.inner(), f_new_data.inner(), del);
2557 pw.consume_file_delta(f_old_id, f_new_id, file_delta(del));
2558 guard.commit();
2559}
2560
2561CMD(mdata, "packet i/o", "ID", "write manifest data packet to stdout")
2562{
2563 if (args.size() != 1)
2564 throw usage(name);
2565
2566 transaction_guard guard(app.db);
2567 packet_writer pw(cout);
2568
2569 manifest_id m_id;
2570 manifest_data m_data;
2571
2572 complete(app, idx(args, 0)(), m_id);
2573
2574 app.db.get_manifest_version(m_id, m_data);
2575 pw.consume_manifest_data(m_id, m_data);
2576 guard.commit();
2577}
2578
2579
2580CMD(fdata, "packet i/o", "ID", "write file data packet to stdout")
2581{
2582 if (args.size() != 1)
2583 throw usage(name);
2584
2585 transaction_guard guard(app.db);
2586 packet_writer pw(cout);
2587
2588 file_id f_id;
2589 file_data f_data;
2590
2591 complete(app, idx(args, 0)(), f_id);
2592
2593 app.db.get_file_version(f_id, f_data);
2594 pw.consume_file_data(f_id, f_data);
2595 guard.commit();
2596}
2597
2598CMD(mcerts, "packet i/o", "ID", "write manifest cert packets to stdout")
2599{
2600 if (args.size() != 1)
2601 throw usage(name);
2602
2603 transaction_guard guard(app.db);
2604 packet_writer pw(cout);
2605
2606 manifest_id m_id;
2607 vector< manifest<cert> > certs;
2608
2609 complete(app, idx(args, 0)(), m_id);
2610
2611 app.db.get_manifest_certs(m_id, certs);
2612 for (size_t i = 0; i < certs.size(); ++i)
2613 pw.consume_manifest_cert(idx(certs, i));
2614 guard.commit();
2615}
2616
2617CMD(fcerts, "packet i/o", "ID", "write file cert packets to stdout")
2618{
2619 if (args.size() != 1)
2620 throw usage(name);
2621
2622 transaction_guard guard(app.db);
2623 packet_writer pw(cout);
2624
2625 file_id f_id;
2626 vector< file<cert> > certs;
2627
2628 complete(app, idx(args, 0)(), f_id);
2629
2630 app.db.get_file_certs(f_id, certs);
2631 for (size_t i = 0; i < certs.size(); ++i)
2632 pw.consume_file_cert(idx(certs, i));
2633 guard.commit();
2634}
2635
2636CMD(pubkey, "packet i/o", "ID", "write public key packet to stdout")
2637{
2638 if (args.size() != 1)
2639 throw usage(name);
2640
2641 transaction_guard guard(app.db);
2642 packet_writer pw(cout);
2643 rsa_keypair_id ident(idx(args, 0)());
2644 base64< rsa_pub_key > key;
2645 app.db.get_key(ident, key);
2646 pw.consume_public_key(ident, key);
2647 guard.commit();
2648}
2649
2650CMD(privkey, "packet i/o", "ID", "write private key packet to stdout")
2651{
2652 if (args.size() != 1)
2653 throw usage(name);
2654
2655 transaction_guard guard(app.db);
2656 packet_writer pw(cout);
2657 rsa_keypair_id ident(idx(args, 0)());
2658 base64< arc4<rsa_priv_key> > key;
2659 app.db.get_key(ident, key);
2660 pw.consume_private_key(ident, key);
2661 guard.commit();
2662}
2663
2664
2665CMD(read, "packet i/o", "", "read packets from stdin")
2666{
2667 transaction_guard guard(app.db);
2668 packet_db_writer dbw(app, true);
2669 size_t count = read_packets(cin, dbw);
2670 N(count != 0, F("no packets found on stdin"));
2671 if (count == 1)
2672 P(F("read 1 packet\n"));
2673 else
2674 P(F("read %d packets\n") % count);
2675 guard.commit();
2676}
2677
2678
2679CMD(agraph, "debug", "", "dump ancestry graph to stdout")
2680{
2681 transaction_guard guard(app.db);
2682
2683 set<string> nodes;
2684 multimap<string,string> branches;
2685 vector< pair<string, string> > edges;
2686
2687 vector< manifest<cert> > certs;
2688 app.db.get_manifest_certs(ancestor_cert_name, certs);
2689
2690 for(vector< manifest<cert> >::iterator i = certs.begin();
2691 i != certs.end(); ++i)
2692 {
2693 cert_value tv;
2694 decode_base64(i->inner().value, tv);
2695 nodes.insert(tv());
2696 nodes.insert(i->inner().ident());
2697 edges.push_back(make_pair(tv(), i->inner().ident()));
2698 }
2699
2700 app.db.get_manifest_certs(branch_cert_name, certs);
2701 for(vector< manifest<cert> >::iterator i = certs.begin();
2702 i != certs.end(); ++i)
2703 {
2704 cert_value tv;
2705 decode_base64(i->inner().value, tv);
2706 nodes.insert(i->inner().ident()); // in case no edges were connected
2707 branches.insert(make_pair(i->inner().ident(), tv()));
2708 }
2709
2710
2711 cout << "graph: " << endl << "{" << endl; // open graph
2712 for (set<string>::iterator i = nodes.begin(); i != nodes.end();
2713 ++i)
2714 {
2715 cout << "node: { title : \"" << *i << "\"\n"
2716 << " label : \"\\fb" << *i;
2717 pair<multimap<string,string>::const_iterator,
2718multimap<string,string>::const_iterator> pair =
2719branches.equal_range(*i);
2720 for (multimap<string,string>::const_iterator j = pair.first;
2721 j != pair.second; ++j)
2722{
2723 cout << "\\n\\fn" << j->second;
2724}
2725 cout << "\"}" << endl;
2726 }
2727 for (vector< pair<string,string> >::iterator i = edges.begin(); i != edges.end();
2728 ++i)
2729 {
2730 cout << "edge: { sourcename : \"" << i->first << "\"" << endl
2731 << " targetname : \"" << i->second << "\" }" << endl;
2732 }
2733 cout << "}" << endl << endl; // close graph
2734 guard.commit();
2735}
2736
2737CMD(fetch, "network", "[URL]", "fetch recent changes from network")
2738{
2739 if (args.size() > 1)
2740 throw usage(name);
2741
2742 set<url> sources;
2743
2744 if (args.size() == 0)
2745 {
2746 if (! app.lua.hook_get_fetch_sources(app.branch_name(), sources))
2747{
2748 P(F("fetching from all known URLs\n"));
2749 app.db.get_all_known_sources(sources);
2750}
2751 }
2752 else
2753 {
2754 url u;
2755 internalize_url(idx(args, 0), u);
2756 sources.insert(u);
2757 }
2758
2759 fetch_queued_blobs_from_network(sources, app);
2760}
2761
2762CMD(post, "network", "[URL]", "post queued changes to network")
2763{
2764 if (args.size() > 1)
2765 throw usage(name);
2766
2767 set<url> targets;
2768 if (args.size() == 0)
2769 {
2770 P(F("no URL provided, posting all queued targets\n"));
2771 app.db.get_queued_targets(targets);
2772 }
2773 else
2774 {
2775 url u;
2776 internalize_url(idx(args, 0), u);
2777 targets.insert(u);
2778 }
2779
2780 post_queued_blobs_to_network(targets, app);
2781}
2782
2783
2784CMD(rcs_import, "rcs", "RCSFILE...", "import all versions in RCS files")
2785{
2786 if (args.size() < 1)
2787 throw usage(name);
2788
2789 transaction_guard guard(app.db);
2790 for (vector<utf8>::const_iterator i = args.begin();
2791 i != args.end(); ++i)
2792 {
2793 import_rcs_file(mkpath((*i)()), app.db);
2794 }
2795 guard.commit();
2796}
2797
2798
2799CMD(cvs_import, "rcs", "CVSROOT", "import all versions in CVS repository")
2800{
2801 if (args.size() != 1)
2802 throw usage(name);
2803
2804 import_cvs_repo(mkpath(idx(args, 0)()), app);
2805}
2806
2807CMD(debug, "debug", "SQL", "issue SQL queries directly (dangerous)")
2808{
2809 if (args.size() != 1)
2810 throw usage(name);
2811 app.db.debug(idx(args, 0)(), cout);
2812}
2813
2814CMD(db, "database", "init\ninfo\nversion\ndump\nload\nmigrate", "manipulate database state")
2815{
2816 if (args.size() != 1)
2817 throw usage(name);
2818 if (idx(args, 0)() == "init")
2819 app.db.initialize();
2820 else if (idx(args, 0)() == "info")
2821 app.db.info(cout);
2822 else if (idx(args, 0)() == "version")
2823 app.db.version(cout);
2824 else if (idx(args, 0)() == "dump")
2825 app.db.dump(cout);
2826 else if (idx(args, 0)() == "load")
2827 app.db.load(cin);
2828 else if (idx(args, 0)() == "migrate")
2829 app.db.migrate();
2830 else
2831 throw usage(name);
2832}
2833
2834
2835}; // namespace commands

Archive Download this file

Branches

Tags

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