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 "network.hh"
25#include "packet.hh"
26#include "patch_set.hh"
27#include "rcs_import.hh"
28#include "sanity.hh"
29#include "cert.hh"
30#include "transforms.hh"
31#include "update.hh"
32#include "vocab.hh"
33#include "work.hh"
34
35//
36// this file defines the task-oriented "top level" commands which can be
37// issued as part of a monotone command line. the command line can only
38// have one such command on it, followed by a vector of strings which are its
39// arguments. all --options will be processed by the main program *before*
40// calling a command
41//
42// we might expose this blunt command interface to scripting someday. but
43// not today.
44
45namespace commands
46{
47  struct command;
48  bool operator<(command const & self, command const & other);
49};
50
51namespace std
52{
53  template <>
54  struct std::greater<commands::command *>
55  {
56    bool operator()(commands::command const * a, commands::command const * b)
57    {
58      return *a < *b;
59    }
60  };
61};
62
63namespace commands
64{
65using namespace std;
66
67struct command;
68
69static map<string,command *> cmds;
70
71struct command
72{
73  string name;
74  string cmdgroup;
75  string params;
76  string desc;
77  command(string const & n,
78	  string const & g,
79	  string const & p,
80	  string const & d) : name(n), cmdgroup(g), params(p), desc(d)
81  { cmds[n] = this; }
82  virtual ~command() {}
83  virtual void exec(app_state & app, vector<string> const & args) = 0;
84};
85
86bool operator<(command const & self, command const & other)
87{
88  return ((self.cmdgroup < other.cmdgroup)
89	  || ((self.cmdgroup == other.cmdgroup) && (self.name < other.name)));
90}
91
92
93void explain_usage(string const & cmd, ostream & out)
94{
95  map<string,command *>::const_iterator i;
96  i = cmds.find(cmd);
97  if (i != cmds.end())
98    {
99      out << "     " << i->second->name << " " << i->second->params << endl
100	  << "     " << i->second->desc << endl << endl;
101      return;
102    }
103
104  vector<command *> sorted;
105  out << "commands:" << endl;
106  for (i = cmds.begin(); i != cmds.end(); ++i)
107    {
108      sorted.push_back(i->second);
109    }
110
111  sort(sorted.begin(), sorted.end(), std::greater<command *>());
112
113  string curr_group;
114  size_t col = 0;
115  size_t col2 = 0;
116  for (size_t i = 0; i < sorted.size(); ++i)
117    {
118      col2 = col2 > sorted[i]->cmdgroup.size() ? col2 : sorted[i]->cmdgroup.size();
119    }
120
121  for (size_t i = 0; i < sorted.size(); ++i)
122    {
123      if (sorted[i]->cmdgroup != curr_group)
124	{
125	  curr_group = sorted[i]->cmdgroup;
126	  out << endl;
127	  out << "  " << sorted[i]->cmdgroup;
128	  col = sorted[i]->cmdgroup.size() + 2;
129	  while (col++ < (col2 + 3))
130	    out << ' ';
131	}
132      out << " " << sorted[i]->name;
133      col += sorted[i]->name.size() + 1;
134      if (col >= 70)
135	{
136	  out << endl;
137	  col = 0;
138	  while (col++ < (col2 + 3))
139	    out << ' ';
140	}
141    }
142  out << endl << endl;
143}
144
145void process(app_state & app, string const & cmd, vector<string> const & args)
146{
147  if (cmds.find(cmd) != cmds.end())
148    {
149      L("executing %s command\n", cmd.c_str());
150      cmds[cmd]->exec(app, args);
151    }
152  else
153    {
154      throw usage(cmd);
155    }
156}
157
158#define CMD(C, group, params, desc)               \
159struct cmd_ ## C : public command                 \
160{                                                 \
161  cmd_ ## C() : command(#C, group, params, desc)  \
162  {}                                              \
163  virtual void exec(app_state & app,              \
164                    vector<string> const & args); \
165};                                                \
166static cmd_ ## C C ## _cmd;                       \
167void cmd_ ## C::exec(app_state & app,             \
168                     vector<string> const & args) \
169
170#define ALIAS(C, realcommand, group, params, desc)	\
171CMD(C, group, params, desc)				\
172{							\
173  process(app, string(#realcommand), args);		\
174}
175
176
177static void ensure_bookdir()
178{
179  mkdir_p(local_path(book_keeping_dir));
180}
181
182static void get_manifest_path(local_path & m_path)
183{
184  m_path = (fs::path(book_keeping_dir) / fs::path(manifest_file_name)).string();
185  L("manifest path is %s\n", m_path().c_str());
186}
187
188static void get_work_path(local_path & w_path)
189{
190  w_path = (fs::path(book_keeping_dir) / fs::path(work_file_name)).string();
191  L("work path is %s\n", w_path().c_str());
192}
193
194static void get_manifest_map(manifest_map & m)
195{
196  ensure_bookdir();
197  local_path m_path;
198  base64< gzip<data> > m_data;
199  get_manifest_path(m_path);
200  if (file_exists(m_path))
201    {
202      L("loading manifest file %s\n", m_path().c_str());
203      read_data(m_path, m_data);
204      read_manifest_map(manifest_data(m_data), m);
205      L("read %d manifest entries\n", m.size());
206    }
207  else
208    {
209      L("no manifest file %s\n", m_path().c_str());
210    }
211}
212
213static void put_manifest_map(manifest_map const & m)
214{
215  ensure_bookdir();
216  local_path m_path;
217  manifest_data m_data;
218  get_manifest_path(m_path);
219  L("writing manifest file %s\n", m_path().c_str());
220  write_manifest_map(m, m_data);
221  write_data(m_path, m_data.inner());
222  L("wrote %d manifest entries\n", m.size());
223}
224
225static void get_work_set(work_set & w)
226{
227  ensure_bookdir();
228  local_path w_path;
229  get_work_path(w_path);
230  if (file_exists(w_path))
231    {
232      L("checking for un-committed work file %s\n",
233	w_path().c_str());
234      data w_data;
235      read_data(w_path, w_data);
236      read_work_set(w_data, w);
237      L("read %d dels, %d adds from %s\n",
238	w.dels.size(), w.adds.size(), w_path().c_str());
239    }
240  else
241    {
242      L("no un-committed work file %s\n", w_path().c_str());
243    }
244}
245
246static void put_work_set(work_set & w)
247{
248  local_path w_path;
249  get_work_path(w_path);
250
251  if (w.adds.size() > 0
252      || w.dels.size() > 0)
253    {
254      ensure_bookdir();
255      data w_data;
256      write_work_set(w_data, w);
257      write_data(w_path, w_data);
258    }
259  else
260    {
261      delete_file(w_path);
262    }
263}
264
265static void calculate_new_manifest_map(manifest_map const & m_old,
266				       manifest_map & m_new)
267{
268  path_set paths;
269  work_set work;
270  extract_path_set(m_old, paths);
271  get_work_set(work);
272  if (work.dels.size() > 0)
273    L("removing %d dead files from manifest\n",
274      work.dels.size());
275  if (work.adds.size() > 0)
276    L("adding %d files to manifest\n", work.adds.size());
277  apply_work_set(work, paths);
278  build_manifest_map(paths, m_new);
279}
280
281static string get_stdin()
282{
283  char buf[bufsz];
284  string tmp;
285  while(cin)
286    {
287      cin.read(buf, bufsz);
288      tmp.append(buf, cin.gcount());
289    }
290  return tmp;
291}
292
293static void get_log_message(patch_set const & ps,
294			    app_state & app,
295			    string & log_message)
296{
297  string commentary;
298  string summary;
299  stringstream ss;
300  patch_set_to_text_summary(ps, ss);
301  summary = ss.str();
302  commentary += "----------------------------------------------------------------------\n";
303  commentary += "Enter Log.  Lines beginning with `MT:' are removed automatically\n";
304  commentary += "\n";
305  commentary += "Summary of changes:\n";
306  commentary += "\n";
307  commentary += summary;
308  commentary += "----------------------------------------------------------------------\n";
309  N(app.lua.hook_edit_comment(commentary, log_message),
310    "edit of log message failed");
311}
312
313
314// the goal here is to look back through the ancestry of the provided
315// child, checking to see the least ancestor it has which we received from
316// the given network url/group pair.
317//
318// we use the ancestor as the source manifest when building a patchset to
319// send to that url/group.
320
321static bool find_ancestor_on_netserver (manifest_id const & child,
322					url const & u,
323					group const & g,
324					manifest_id & anc,
325					app_state & app)
326{
327  set<manifest_id> frontier;
328  cert_name tn(ancestor_cert_name);
329  frontier.insert(child);
330
331  while (!frontier.empty())
332    {
333      set<manifest_id> next_frontier;
334      for (set<manifest_id>::const_iterator i = frontier.begin();
335	   i != frontier.end(); ++i)
336	{
337	  vector< manifest<cert> > tmp;
338	  app.db.get_manifest_certs(*i, tn, tmp);
339
340	  // we go through this vector backwards because we would prefer to
341	  // hit more recently-queued ancestors (such as intermediate nodes
342	  // in a multi-node merge) rather than older ancestors. but of
343	  // course, any ancestor will do.
344
345	  for (vector< manifest<cert> >::reverse_iterator j = tmp.rbegin();
346	       j != tmp.rend(); ++j)
347	    {
348	      cert_value tv;
349	      decode_base64(j->inner().value, tv);
350	      manifest_id anc_id (tv());
351
352	      L("looking for parent %s of %s on server\n",
353		i->inner()().c_str(),
354		anc_id.inner()().c_str());
355
356	      if (app.db.manifest_exists_on_netserver (u, g, anc_id))
357		{
358		  L("found parent %s on server\n", anc_id.inner()().c_str());
359		  anc = anc_id;
360		  return true;
361		}
362	      else
363		next_frontier.insert(anc_id);
364	    }
365	}
366
367      frontier = next_frontier;
368    }
369
370  return false;
371}
372
373
374static void queue_edge_for_target_ancestor (pair<url,group> const & targ,
375					    manifest_id const & child_id,
376					    manifest_map const & child_map,
377					    app_state & app)
378{
379  // now here is an interesting thing: we *might* be sending data to a
380  // depot, or someone with indeterminate pre-existing state (say the first
381  // time we post to netnews), therefore we cannot just "send the edge" we
382  // just constructed in a merge or commit, we need to send an edge from a
383  // parent which we know to be present in the depot (or else an edge from
384  // the empty map -- full contents of all files). what is sent therefore
385  // changes on a depot-by-depot basis. this function calculates the
386  // appropriate thing to send.
387  //
388  // nb: this has no direct relation to what we store in our own
389  // database. we always store the edge from our parent, and we always know
390  // when we have a parent.
391
392  vector< pair<url, group> > one_target;
393  one_target.push_back(targ);
394  queueing_packet_writer qpw(app, one_target);
395
396  manifest_data targ_ancestor_data;
397  manifest_map targ_ancestor_map;
398  manifest_id targ_ancestor_id;
399
400  if (find_ancestor_on_netserver (child_id,
401				  targ.first,
402				  targ.second,
403				  targ_ancestor_id,
404				  app))
405    {
406      app.db.get_manifest_version(targ_ancestor_id, targ_ancestor_data);
407      read_manifest_map(targ_ancestor_data, targ_ancestor_map);
408    }
409
410  patch_set ps;
411  manifests_to_patch_set(targ_ancestor_map, child_map, app, ps);
412  patch_set_to_packets(ps, app, qpw);
413
414  // now that we've queued the data, we can note this new child
415  // node as existing (well .. soon-to-exist) on the server
416  app.db.note_manifest_on_netserver (targ.first, targ.second, child_id);
417
418}
419
420
421// this helper tries to produce merge <- mergeN(left,right), possibly
422// merge3 if it can find an ancestor, otherwise merge2. it also queues the
423// appropriate edges from known ancestors to the new merge node, to be
424// transmitted to each of the targets provided.
425
426static void try_one_merge(manifest_id const & left,
427			  manifest_id const & right,
428			  manifest_id & merged,
429			  app_state & app,
430			  vector< pair<url,group> > const & targets)
431{
432  manifest_data left_data, right_data, ancestor_data, merged_data;
433  manifest_map left_map, right_map, ancestor_map, merged_map;
434  manifest_id ancestor;
435
436  app.db.get_manifest_version(left, left_data);
437  app.db.get_manifest_version(right, right_data);
438  read_manifest_map(left_data, left_map);
439  read_manifest_map(right_data, right_map);
440
441  simple_merge_provider merger(app);
442
443  if(find_common_ancestor(left, right, ancestor, app))
444    {
445      P("common ancestor %s found, trying merge3\n", ancestor.inner()().c_str());
446      app.db.get_manifest_version(ancestor, ancestor_data);
447      read_manifest_map(ancestor_data, ancestor_map);
448      N(merge3(ancestor_map, left_map, right_map,
449	       app, merger, merged_map),
450	(string("failed to merge manifests ")
451	 + left.inner()() + " and " + right.inner()()));
452    }
453  else
454    {
455      P("no common ancestor found, trying merge2\n");
456      N(merge2(left_map, right_map, app, merger, merged_map),
457	(string("failed to merge manifests ")
458	 + left.inner()() + " and " + right.inner()()));
459    }
460
461  write_manifest_map(merged_map, merged_data);
462  calculate_manifest_map_ident(merged_map, merged);
463
464  base64< gzip<delta> > left_edge;
465  diff(left_data.inner(), merged_data.inner(), left_edge);
466
467  // FIXME: we do *not* manufacture or store the second edge to
468  // the merged version, since doing so violates the
469  // assumptions of the db, and the 'right' version already
470  // exists in its entirety, anyways. this is a subtle issue
471  // though and I'm not sure I'm making the right
472  // decision. revisit. if you do not see that it is a subtle
473  // issue I suggest you are not thinking about it long enough.
474  //
475  // base64< gzip<delta> > right_edge;
476  // diff(right_data.inner(), merged_data.inner(), right_edge);
477  // app.db.put_manifest_version(right, merged, right_edge);
478
479
480  // we do of course record the left edge, and ancestry relationship to
481  // both predecessors.
482
483  {
484    packet_db_writer dbw(app);
485
486    dbw.consume_manifest_delta(left, merged, left_edge);
487    cert_manifest_ancestor(left, merged, app, dbw);
488    cert_manifest_ancestor(right, merged, app, dbw);
489    cert_manifest_date_now(merged, app, dbw);
490    cert_manifest_author_default(merged, app, dbw);
491
492    // make sure the appropriate edges get queued for the network.
493    for (vector< pair<url,group> >::const_iterator targ = targets.begin();
494	 targ != targets.end(); ++targ)
495      {
496	queue_edge_for_target_ancestor (*targ, merged, merged_map, app);
497      }
498
499    queueing_packet_writer qpw(app, targets);
500    cert_manifest_ancestor(left, merged, app, qpw);
501    cert_manifest_ancestor(right, merged, app, qpw);
502    cert_manifest_date_now(merged, app, qpw);
503    cert_manifest_author_default(merged, app, qpw);
504  }
505
506}
507
508
509// actual commands follow
510
511CMD(lscerts, "key and cert", "(file|manifest) <id>",
512    "list certs associated with manifest or file")
513{
514  if (args.size() != 2)
515    throw usage(name);
516
517  vector<cert> certs;
518
519  transaction_guard guard(app.db);
520
521  if (args[0] == "manifest")
522    {
523      manifest_id ident(args[1]);
524      vector< manifest<cert> > ts;
525      app.db.get_manifest_certs(ident, ts);
526      for (size_t i = 0; i < ts.size(); ++i)
527	certs.push_back(ts[i].inner());
528    }
529  else if (args[0] == "file")
530    {
531      file_id ident(args[1]);
532      vector< file<cert> > ts;
533      app.db.get_file_certs(ident, ts);
534      for (size_t i = 0; i < ts.size(); ++i)
535	certs.push_back(ts[i].inner());
536    }
537  else
538    throw usage(name);
539
540  for (size_t i = 0; i < certs.size(); ++i)
541    {
542      bool ok = check_cert(app, certs[i]);
543      cert_value tv;
544      decode_base64(certs[i].value, tv);
545      string washed;
546      if (guess_binary(tv()))
547	{
548	  washed = "<binary data>";
549	}
550      else
551	{
552	  washed = tv();
553	}
554      string head = string(ok ? "ok sig from " : "bad sig from ")
555	+ "[" + certs[i].key() + "] : "
556	+ "[" + certs[i].name() + "] = [";
557      string pad(head.size(), ' ');
558      vector<string> lines;
559      split_into_lines(washed, lines);
560      I(lines.size() > 0);
561      cout << head << lines[0] ;
562      for (size_t i = 1; i < lines.size(); ++i)
563	cout << endl << pad << lines[i];
564      cout << "]" << endl;
565    }
566  guard.commit();
567}
568
569CMD(lskeys, "key and cert", "[partial-id]", "list keys")
570{
571  vector<rsa_keypair_id> pubkeys;
572  vector<rsa_keypair_id> privkeys;
573
574  transaction_guard guard(app.db);
575
576  if (args.size() == 0)
577    app.db.get_key_ids("", pubkeys, privkeys);
578  else if (args.size() == 1)
579    app.db.get_key_ids(args[0], pubkeys, privkeys);
580  else
581    throw usage(name);
582
583  if (pubkeys.size() > 0)
584    {
585      cout << endl << "[public keys]" << endl;
586      for (size_t i = 0; i < pubkeys.size(); ++i)
587	cout << pubkeys[i]() << endl;
588      cout << endl;
589    }
590
591  if (privkeys.size() > 0)
592    {
593      cout << endl << "[private keys]" << endl;
594      for (size_t i = 0; i < privkeys.size(); ++i)
595	cout << privkeys[i]() << endl;
596      cout << endl;
597    }
598
599  guard.commit();
600}
601
602CMD(genkey, "key and cert", "<keyid>", "generate an RSA key-pair")
603{
604  if (args.size() != 1)
605    throw usage(name);
606
607  transaction_guard guard(app.db);
608  rsa_keypair_id ident(args[0]);
609
610  N(! app.db.key_exists(ident),
611    (string("key '") + ident() + "' already exists in database"));
612
613  base64<rsa_pub_key> pub;
614  base64< arc4<rsa_priv_key> > priv;
615  P("generating key-pair '%s'\n", ident().c_str());
616  generate_key_pair(app.lua, ident, pub, priv);
617  P("storing key-pair '%s' in database\n", ident().c_str());
618  app.db.put_key_pair(ident, pub, priv);
619
620  guard.commit();
621}
622
623CMD(cert, "key and cert", "(file|manifest) <id> <certname> [certval]",
624                        "create a cert for a file or manifest")
625{
626  if ((args.size() != 4) && (args.size() != 3))
627    throw usage(name);
628
629  transaction_guard guard(app.db);
630
631  hexenc<id> ident(args[1]);
632  cert_name name(args[2]);
633
634  rsa_keypair_id key;
635  if (app.signing_key() != "")
636    key = app.signing_key;
637  else
638    N(guess_default_key(key, app),
639      "no unique private key found, and no key specified");
640
641  cert_value val;
642  if (args.size() == 4)
643    val = cert_value(args[3]);
644  else
645    val = cert_value(get_stdin());
646
647  base64<cert_value> val_encoded;
648  encode_base64(val, val_encoded);
649
650  cert t(ident, name, val_encoded, key);
651
652  // nb: we want to throw usage on mis-use *before* asking for a
653  // passphrase.
654
655  if (args[0] == "file")
656    {
657      calculate_cert(app, t);
658      app.db.put_file_cert(file<cert>(t));
659    }
660  else if (args[0] == "manifest")
661    {
662      calculate_cert(app, t);
663      app.db.put_manifest_cert(manifest<cert>(t));
664    }
665  else
666    throw usage(this->name);
667
668  guard.commit();
669}
670
671
672CMD(tag, "certificate", "<id> <tagname>",
673    "put a symbolic tag cert on a manifest version")
674{
675  if (args.size() != 2)
676    throw usage(name);
677  manifest_id m(args[0]);
678  packet_db_writer dbw(app);
679  cert_manifest_tag(m, args[1], app, dbw);
680}
681
682CMD(approve, "certificate", "(file|manifest) <id>",
683    "approve of a manifest or file version")
684{
685  if (args.size() != 2)
686    throw usage(name);
687  if (args[0] == "manifest")
688    {
689      manifest_id m(args[1]);
690      packet_db_writer dbw(app);
691      cert_manifest_approval(m, true, app, dbw);
692    }
693  else if (args[0] == "file")
694    {
695      packet_db_writer dbw(app);
696      file_id f(args[1]);
697      cert_file_approval(f, true, app, dbw);
698    }
699  else
700    throw usage(name);
701}
702
703CMD(disapprove, "certificate", "(file|manifest) <id>",
704    "disapprove of a manifest or file version")
705{
706  if (args.size() != 2)
707    throw usage(name);
708  if (args[0] == "manifest")
709    {
710      manifest_id m(args[1]);
711      packet_db_writer dbw(app);
712      cert_manifest_approval(m, false, app, dbw);
713    }
714  else if (args[0] == "file")
715    {
716      file_id f(args[1]);
717      packet_db_writer dbw(app);
718      cert_file_approval(f, false, app, dbw);
719    }
720  else
721    throw usage(name);
722}
723
724
725CMD(comment, "certificate", "(file|manifest) <id> [comment]",
726    "comment on a file or manifest version")
727{
728  if (args.size() != 2 && args.size() != 3)
729    throw usage(name);
730
731  string comment;
732  if (args.size() == 3)
733    comment = args[2];
734  else
735    N(app.lua.hook_edit_comment("", comment), "edit comment failed");
736
737  N(comment.find_first_not_of(" \r\t\n") == string::npos, "empty comment");
738
739  if (args[0] == "file")
740    {
741      packet_db_writer dbw(app);
742      cert_file_comment(file_id(args[1]), comment, app, dbw);
743    }
744  else if (args[0] == "manifest")
745    {
746      packet_db_writer dbw(app);
747      cert_manifest_comment(manifest_id(args[1]), comment, app, dbw);
748    }
749  else
750    throw usage(name);
751}
752
753
754
755CMD(add, "working copy", "<pathname> [...]", "add files to working copy")
756{
757  if (args.size() < 1)
758    throw usage(name);
759
760  transaction_guard guard(app.db);
761
762  manifest_map man;
763  work_set work;
764  get_manifest_map(man);
765  get_work_set(work);
766  bool rewrite_work = false;
767
768  for (vector<string>::const_iterator i = args.begin(); i != args.end(); ++i)
769    build_addition(file_path(*i), app, work, man, rewrite_work);
770
771  guard.commit();
772
773  // small race here
774  if (rewrite_work)
775    put_work_set(work);
776}
777
778CMD(drop, "working copy", "<pathname> [...]", "drop files from working copy")
779{
780  if (args.size() < 1)
781    throw usage(name);
782
783  manifest_map man;
784  work_set work;
785  get_manifest_map(man);
786  get_work_set(work);
787  bool rewrite_work = false;
788
789  transaction_guard guard(app.db);
790
791  for (vector<string>::const_iterator i = args.begin(); i != args.end(); ++i)
792    build_deletion(file_path(*i), app, work, man, rewrite_work);
793
794  guard.commit();
795
796  // small race here
797  if (rewrite_work)
798    put_work_set(work);
799}
800
801CMD(commit, "working copy", "[log message]", "commit working copy to database")
802{
803  string log_message("");
804  manifest_map m_old, m_new;
805  patch_set ps;
806
807  get_manifest_map(m_old);
808  calculate_new_manifest_map(m_old, m_new);
809  manifest_id old_id, new_id;
810  calculate_manifest_map_ident(m_old, old_id);
811  calculate_manifest_map_ident(m_new, new_id);
812
813  if (args.size() != 0 && args.size() != 1)
814    throw usage(name);
815
816  cert_value branchname;
817  if (app.branch_name != "")
818    {
819      branchname = app.branch_name;
820    }
821  else
822    {
823      vector< manifest<cert> > certs;
824      cert_name branch(branch_cert_name);
825      app.db.get_manifest_certs(old_id, branch, certs);
826
827      N(certs.size() != 0,
828	string("no branch certs found for old manifest ")
829	+ old_id.inner()() + ", please provide a branch name");
830
831      N(certs.size() == 1,
832	string("multiple branch certs found for old manifest ")
833	+ old_id.inner()() + ", please provide a branch name");
834
835      decode_base64(certs[0].inner().value, branchname);
836    }
837
838  L("committing %s to branch %s\n",
839    new_id.inner()().c_str(), branchname().c_str());
840  app.branch_name = branchname();
841
842  manifests_to_patch_set(m_old, m_new, app, ps);
843
844  // get log message
845  if (args.size() == 1)
846    log_message = args[0];
847  else
848    get_log_message(ps, app, log_message);
849
850  N(log_message.find_first_not_of(" \r\t\n") != string::npos,
851    "empty log message");
852
853  {
854    transaction_guard guard(app.db);
855
856    // process manifest delta or new manifest
857    if (app.db.manifest_version_exists(ps.m_new))
858      {
859	L("skipping manifest %s, already in database\n", ps.m_new.inner()().c_str());
860      }
861    else
862      {
863	if (app.db.manifest_version_exists(ps.m_old))
864	  {
865	    L("inserting manifest delta %s -> %s\n",
866	      ps.m_old.inner()().c_str(), ps.m_new.inner()().c_str());
867	    manifest_data m_old_data, m_new_data;
868	    app.db.get_manifest_version(ps.m_old, m_old_data);
869	    write_manifest_map(m_new, m_new_data);
870	    base64< gzip<delta> > del;
871	    diff(m_old_data.inner(), m_new_data.inner(), del);
872	    app.db.put_manifest_version(ps.m_old, ps.m_new, manifest_delta(del));
873	  }
874	else
875	  {
876	    L("inserting full manifest %s\n",
877	      ps.m_new.inner()().c_str());
878	    manifest_data m_new_data;
879	    write_manifest_map(m_new, m_new_data);
880	    app.db.put_manifest(ps.m_new, m_new_data);
881	  }
882      }
883
884    // process file deltas
885    for (set<patch_delta>::const_iterator i = ps.f_deltas.begin();
886	 i != ps.f_deltas.end(); ++i)
887      {
888	if (app.db.file_version_exists(i->id_new))
889	  {
890	    L("skipping file delta %s, already in database\n", i->id_new.inner()().c_str());
891	  }
892	else
893	  {
894	    if (app.db.file_version_exists(i->id_old))
895	      {
896		L("inserting delta %s -> %s\n",
897		  i->id_old.inner()().c_str(), i->id_new.inner()().c_str());
898		file_data old_data;
899		base64< gzip<data> > new_data;
900		app.db.get_file_version(i->id_old, old_data);
901		read_data(i->path, new_data);
902		base64< gzip<delta> > del;
903		diff(old_data.inner(), new_data, del);
904		app.db.put_file_version(i->id_old, i->id_new, file_delta(del));
905	      }
906	    else
907	      {
908		L("inserting full version %s\n", i->id_old.inner()().c_str());
909		base64< gzip<data> > new_data;
910		read_data(i->path, new_data);
911		// sanity check
912		hexenc<id> tid;
913		calculate_ident(new_data, tid);
914		I(tid == i->id_new.inner());
915		app.db.put_file(i->id_new, file_data(new_data));
916	      }
917	  }
918      }
919
920    // process file adds
921    for (set<patch_addition>::const_iterator i = ps.f_adds.begin();
922	 i != ps.f_adds.end(); ++i)
923      {
924	if (app.db.file_version_exists(i->ident))
925	  {
926	    L("skipping file %s %s, already in database\n",
927	      i->path().c_str(), i->ident.inner()().c_str());
928	  }
929	else
930	  {
931	    // it's a new file
932	    L("inserting new file %s %s\n",
933	      i->path().c_str(), i->ident.inner()().c_str());
934	    base64< gzip<data> > new_data;
935	    read_data(i->path, new_data);
936	    app.db.put_file(i->ident, new_data);
937	  }
938      }
939
940    packet_db_writer dbw(app);
941
942    if (! m_old.empty())
943      cert_manifest_ancestor(ps.m_old, ps.m_new, app, dbw);
944
945    cert_manifest_in_branch(ps.m_new, branchname, app, dbw);
946    cert_manifest_date_now(ps.m_new, app, dbw);
947    cert_manifest_author_default(ps.m_new, app, dbw);
948    cert_manifest_changelog(ps.m_new, log_message, app, dbw);
949
950    // commit done, now queue diff for sending
951
952    if (app.db.manifest_version_exists(ps.m_new))
953      {
954	vector< pair<url,group> > targets;
955	app.lua.hook_get_post_targets(branchname, targets);
956
957	// make sure the appropriate edges get queued for the network.
958	for (vector< pair<url,group> >::const_iterator targ = targets.begin();
959	     targ != targets.end(); ++targ)
960	  {
961	    queue_edge_for_target_ancestor (*targ, ps.m_new, m_new, app);
962	  }
963
964	// throw in all available certs for good measure
965	queueing_packet_writer qpw(app, targets);
966	vector< manifest<cert> > certs;
967	app.db.get_manifest_certs(ps.m_new, certs);
968	for(vector< manifest<cert> >::const_iterator i = certs.begin();
969	    i != certs.end(); ++i)
970	  qpw.consume_manifest_cert(*i);
971      }
972
973    guard.commit();
974  }
975  // small race condition here...
976  local_path w_path;
977  get_work_path(w_path);
978  delete_file(w_path);
979  put_manifest_map(m_new);
980  P("committed %s\n", ps.m_new.inner()().c_str());
981}
982
983CMD(update, "working copy", "[sort keys...]", "update working copy, relative to sorting keys")
984{
985
986  manifest_data m_chosen_data;
987  manifest_map m_old, m_working, m_chosen, m_new;
988  manifest_id m_old_id, m_chosen_id;
989
990  transaction_guard guard(app.db);
991
992  get_manifest_map(m_old);
993  calculate_manifest_map_ident(m_old, m_old_id);
994  calculate_new_manifest_map(m_old, m_working);
995
996  pick_update_target(m_old_id, args, app, m_chosen_id);
997  P("selected update target %s\n",
998    m_chosen_id.inner()().c_str());
999  app.db.get_manifest_version(m_chosen_id, m_chosen_data);
1000  read_manifest_map(m_chosen_data, m_chosen);
1001
1002  update_merge_provider merger(app);
1003  N(merge3(m_old, m_chosen, m_working, app, merger, m_new),
1004    string("manifest merge failed, no update performed"));
1005
1006  P("calculating patchset for update\n");
1007  patch_set ps;
1008  manifests_to_patch_set(m_working, m_new, app, ps);
1009
1010  L("applying %d deletions to files in tree\n", ps.f_dels.size());
1011  for (set<file_path>::const_iterator i = ps.f_dels.begin();
1012       i != ps.f_dels.end(); ++i)
1013    {
1014      L("deleting %s\n", (*i)().c_str());
1015      delete_file(*i);
1016    }
1017
1018  L("applying %d moves to files in tree\n", ps.f_moves.size());
1019  for (set<patch_move>::const_iterator i = ps.f_moves.begin();
1020       i != ps.f_moves.end(); ++i)
1021    {
1022      L("moving %s -> %s\n", i->path_old().c_str(), i->path_new().c_str());
1023      move_file(i->path_old, i->path_new);
1024    }
1025
1026  L("applying %d additions to tree\n", ps.f_adds.size());
1027  for (set<patch_addition>::const_iterator i = ps.f_adds.begin();
1028       i != ps.f_adds.end(); ++i)
1029    {
1030      L("adding %s as %s\n", i->ident.inner()().c_str(), i->path().c_str());
1031      file_data tmp;
1032      if (app.db.file_version_exists(i->ident))
1033	app.db.get_file_version(i->ident, tmp);
1034      else if (merger.temporary_store.find(i->ident) != merger.temporary_store.end())
1035	tmp = merger.temporary_store[i->ident];
1036      else
1037	I(false); // trip assert. this should be impossible.
1038      write_data(i->path, tmp.inner());
1039    }
1040
1041  L("applying %d deltas to tree\n", ps.f_deltas.size());
1042  for (set<patch_delta>::const_iterator i = ps.f_deltas.begin();
1043       i != ps.f_deltas.end(); ++i)
1044    {
1045      P("updating file %s: %s -> %s\n",
1046	i->path().c_str(),
1047	i->id_old.inner()().c_str(),
1048	i->id_new.inner()().c_str());
1049
1050      // sanity check
1051      {
1052	base64< gzip<data> > dtmp;
1053	hexenc<id> dtmp_id;
1054	read_data(i->path, dtmp);
1055	calculate_ident(dtmp, dtmp_id);
1056	I(dtmp_id == i->id_old.inner());
1057      }
1058
1059      // ok, replace with new version
1060      {
1061	file_data tmp;
1062	if (app.db.file_version_exists(i->id_new))
1063	  app.db.get_file_version(i->id_new, tmp);
1064	else if (merger.temporary_store.find(i->id_new) != merger.temporary_store.end())
1065	  tmp = merger.temporary_store[i->id_new];
1066	else
1067	  I(false); // trip assert. this should be impossible.
1068	write_data(i->path, tmp.inner());
1069      }
1070    }
1071
1072  L("update successful\n");
1073  guard.commit();
1074
1075  // small race condition here...
1076  // nb: we write out m_chosen, not m_new, because the manifest-on-disk
1077  // is the basis of the working copy, not the working copy itself.
1078  put_manifest_map(m_chosen);
1079  P("updated to base version %s\n", m_chosen_id.inner()().c_str());
1080}
1081
1082
1083
1084CMD(cat, "tree", "(file|manifest) <id>", "write file or manifest from database to stdout")
1085{
1086  if (args.size() != 2)
1087    throw usage(name);
1088
1089  transaction_guard guard(app.db);
1090
1091  if (args[0] == "file")
1092    {
1093      file_data dat;
1094      file_id ident(args[1]);
1095
1096      N(app.db.file_version_exists(ident),
1097	(string("no file version ") + ident.inner()() + " found in database"));
1098
1099      L("dumping file %s\n", ident.inner()().c_str());
1100      app.db.get_file_version(ident, dat);
1101      data unpacked;
1102      unpack(dat.inner(), unpacked);
1103      cout.write(unpacked().data(), unpacked().size());
1104
1105    }
1106  else if (args[0] == "manifest")
1107    {
1108      manifest_data dat;
1109      manifest_id ident(args[1]);
1110
1111      N(app.db.manifest_version_exists(ident),
1112	(string("no file version ") + ident.inner()() + " found in database"));
1113
1114      L("dumping manifest %s\n", ident.inner()().c_str());
1115      app.db.get_manifest_version(ident, dat);
1116      data unpacked;
1117      unpack(dat.inner(), unpacked);
1118      cout.write(unpacked().data(), unpacked().size());
1119    }
1120  else
1121    throw usage(name);
1122
1123  guard.commit();
1124}
1125
1126
1127CMD(checkout, "tree", "<manifest-id>", "check out tree state from database")
1128{
1129  if (args.size() != 1)
1130    throw usage(name);
1131
1132  transaction_guard guard(app.db);
1133
1134  file_data data;
1135  manifest_id ident(args[0]);
1136  manifest_map m;
1137
1138  N(app.db.manifest_version_exists(ident),
1139    (string("no manifest version ") + ident.inner()() + " found in database"));
1140
1141  L("exporting manifest %s\n", ident.inner()().c_str());
1142  manifest_data m_data;
1143  app.db.get_manifest_version(ident, m_data);
1144  read_manifest_map(m_data, m);
1145  put_manifest_map(m);
1146
1147  for (manifest_map::const_iterator i = m.begin(); i != m.end(); ++i)
1148    {
1149      vector<string> args;
1150      path_id_pair pip(*i);
1151
1152      N(app.db.file_version_exists(pip.ident()),
1153	(string("no file version ")
1154	 + pip.ident().inner()()
1155	 + " found in database for "
1156	 + pip.path()().c_str()));
1157
1158      file_data dat;
1159      L("writing file %s to %s\n",
1160	pip.ident().inner()().c_str(),
1161	pip.path()().c_str());
1162      app.db.get_file_version(pip.ident(), dat);
1163      write_data(pip.path(), dat.inner());
1164    }
1165
1166  guard.commit();
1167}
1168
1169ALIAS(co, checkout, "tree", "<manifest-id>",
1170      "check out tree state from database; alias for checkout")
1171
1172CMD(heads, "tree", "", "show unmerged heads of branch")
1173{
1174  vector<manifest_id> heads;
1175  if (args.size() != 0)
1176    throw usage(name);
1177
1178  if (app.branch_name == "")
1179    {
1180      cout << "please specify a branch, with --branch=<branchname>" << endl;
1181      return;
1182    }
1183
1184  get_branch_heads(app.branch_name, app, heads);
1185
1186  if (heads.size() == 0)
1187    cout << "branch '" << app.branch_name << "' is empty" << endl;
1188  else if (heads.size() == 1)
1189    cout << "branch '" << app.branch_name << "' is currently merged:" << endl;
1190  else
1191    cout << "branch '" << app.branch_name << "' is currently unmerged:" << endl;
1192
1193  for (vector<manifest_id>::const_iterator i = heads.begin();
1194       i != heads.end(); ++i)
1195    {
1196      cout << i->inner()() << endl;
1197    }
1198}
1199
1200
1201CMD(merge, "tree", "", "merge unmerged heads of branch")
1202{
1203
1204  vector<manifest_id> heads;
1205
1206  if (args.size() != 0)
1207    throw usage(name);
1208
1209  if (app.branch_name == "")
1210    {
1211      cout << "please specify a branch, with --branch=<branchname>" << endl;
1212      return;
1213    }
1214
1215  get_branch_heads(app.branch_name, app, heads);
1216
1217  if (heads.size() == 0)
1218    {
1219      cout << "branch " << args[0] << "is empty" << endl;
1220      return;
1221    }
1222  else if (heads.size() == 1)
1223    {
1224      cout << "branch " << args[0] << "is merged" << endl;
1225      return;
1226    }
1227  else
1228    {
1229      vector< pair<url,group> > targets;
1230      app.lua.hook_get_post_targets(app.branch_name, targets);
1231
1232      manifest_id left = heads[0];
1233      manifest_id ancestor;
1234      for (size_t i = 1; i < heads.size(); ++i)
1235	{
1236	  manifest_id right = heads[i];
1237	  P("merging with manifest %d / %d: %s <-> %s\n",
1238	    i, heads.size(),
1239	    left.inner()().c_str(), right.inner()().c_str());
1240
1241	  manifest_id merged;
1242	  transaction_guard guard(app.db);
1243	  try_one_merge (left, right, merged, app, targets);
1244
1245	  // merged 1 edge; now we commit this, update merge source and
1246	  // try next one
1247
1248	  packet_db_writer dbw(app);
1249	  queueing_packet_writer qpw(app, targets);
1250	  cert_manifest_in_branch(merged, app.branch_name, app, dbw);
1251	  cert_manifest_in_branch(merged, app.branch_name, app, qpw);
1252
1253	  string log = "merge of " + left.inner()() + " and " + right.inner()();
1254	  cert_manifest_changelog(merged, log, app, dbw);
1255	  cert_manifest_changelog(merged, log, app, qpw);
1256
1257	  guard.commit();
1258	  P("[source] %s\n[source] %s\n[merged] %s\n",
1259	    left.inner()().c_str(),
1260	    right.inner()().c_str(),
1261	    merged.inner()().c_str());
1262	  left = merged;
1263	}
1264    }
1265}
1266
1267
1268CMD(propagate, "tree", "<src-branch> <dst-branch>",
1269    "merge from one branch to another asymmetrically")
1270{
1271  /*
1272
1273  this is a special merge operator, but very useful for people maintaining
1274  "slightly disparate but related" trees. it does a one-way merge; less
1275  powerful than putting things in the same branch and also more flexible.
1276
1277  1. check to see if src and dst branches are merged, if not abort, if so
1278     call heads N1 and N2 respectively.
1279
1280  2. (FIXME: not yet present) run the hook propagate ("src-branch",
1281     "dst-branch", N1, N2) which gives the user a chance to massage N1 into
1282     a state which is likely to "merge nicely" with N2, eg. edit pathnames,
1283     omit optional files of no interest.
1284
1285  3. do a normal 2 or 3-way merge on N1 and N2, depending on the
1286     existence of common ancestors.
1287
1288  4. save the results as the delta (N2,M), the ancestry edges (N1,M)
1289     and (N2,M), and the cert (N2,dst).
1290
1291  5. queue the resulting packets to send to the url for dst-branch, not
1292     src-branch.
1293
1294  */
1295
1296  vector<manifest_id> src_heads, dst_heads;
1297
1298  if (args.size() != 2)
1299    throw usage(name);
1300
1301  get_branch_heads(args[0], app, src_heads);
1302  get_branch_heads(args[1], app, dst_heads);
1303
1304  if (src_heads.size() == 0)
1305    {
1306      cout << "branch " << args[0] << "is empty" << endl;
1307      return;
1308    }
1309  else if (src_heads.size() != 1)
1310    {
1311      cout << "branch " << args[0] << "is not merged" << endl;
1312      return;
1313    }
1314  else if (dst_heads.size() == 0)
1315    {
1316      cout << "branch " << args[1] << "is empty" << endl;
1317      return;
1318    }
1319  else if (dst_heads.size() != 1)
1320    {
1321      cout << "branch " << args[1] << "is not merged" << endl;
1322      return;
1323    }
1324  else
1325    {
1326      vector< pair<url,group> > targets;
1327      app.lua.hook_get_post_targets(args[1], targets);
1328
1329      manifest_id merged;
1330      transaction_guard guard(app.db);
1331      try_one_merge (src_heads[0], dst_heads[0], merged, app, targets);
1332
1333      queueing_packet_writer qpw(app, targets);
1334      cert_manifest_in_branch(merged, app.branch_name, app, qpw);
1335      cert_manifest_changelog(merged,
1336			      "propagate of "
1337			      + src_heads[0].inner()()
1338			      + " and "
1339			      + dst_heads[0].inner()()
1340			      + "\n"
1341			      + "from branch "
1342			      + args[0] + " to " + args[1] + "\n",
1343			      app, qpw);
1344      guard.commit();
1345    }
1346}
1347
1348
1349
1350CMD(diff, "informative", "", "show current diffs on stdout")
1351{
1352  manifest_map m_old, m_new;
1353  patch_set ps;
1354
1355  transaction_guard guard(app.db);
1356
1357  if (args.size() > 0)
1358    {
1359      manifest_data dat;
1360      manifest_id ident(args[0]);
1361
1362      N(app.db.manifest_version_exists(ident),
1363	(string("no file version ") + ident.inner()() + " found in database"));
1364
1365      L("getting manifest %s\n", ident.inner()().c_str());
1366      app.db.get_manifest_version(ident, dat);
1367      read_manifest_map(dat, m_old);
1368    }
1369  else
1370    get_manifest_map(m_old);
1371
1372  if (args.size() > 1)
1373    {
1374      manifest_data dat;
1375      manifest_id ident(args[1]);
1376
1377      N(app.db.manifest_version_exists(ident),
1378	(string("no file version ") + ident.inner()() + " found in database"));
1379
1380      L("getting manifest %s\n", ident.inner()().c_str());
1381      app.db.get_manifest_version(ident, dat);
1382      read_manifest_map(dat, m_new);
1383    }
1384  else
1385    calculate_new_manifest_map(m_old, m_new);
1386
1387  manifests_to_patch_set(m_old, m_new, app, ps);
1388
1389  for (set<patch_delta>::const_iterator i = ps.f_deltas.begin();
1390       i != ps.f_deltas.end(); ++i)
1391    {
1392      file_data f_old, f_new;
1393      gzip<data> decoded_old, decoded_new;
1394      data decompressed_old, decompressed_new;
1395      vector<string> old_lines, new_lines;
1396
1397      app.db.get_file_version(i->id_old, f_old);
1398      decode_base64(f_old.inner(), decoded_old);
1399      decode_gzip(decoded_old, decompressed_old);
1400
1401      if (args.size() > 1)
1402	{
1403	  app.db.get_file_version(i->id_new, f_new);
1404	  decode_base64(f_new.inner(), decoded_new);
1405	  decode_gzip(decoded_new, decompressed_new);
1406	}
1407      else
1408	read_data(i->path, decompressed_new);
1409
1410      split_into_lines(decompressed_old(), old_lines);
1411      split_into_lines(decompressed_new(), new_lines);
1412
1413      unidiff(i->path(), i->path(), old_lines, new_lines, cout);
1414    }
1415  guard.commit();
1416}
1417
1418CMD(status, "informative", "", "show status of working copy")
1419{
1420  manifest_map m_old, m_new;
1421  patch_set ps;
1422
1423  transaction_guard guard(app.db);
1424  get_manifest_map(m_old);
1425  calculate_new_manifest_map(m_old, m_new);
1426  manifests_to_patch_set(m_old, m_new, app, ps);
1427  patch_set_to_text_summary(ps, cout);
1428  guard.commit();
1429}
1430
1431
1432CMD(mdelta, "packet i/o", "<oldid> <newid>", "write manifest delta packet to stdout")
1433{
1434  if (args.size() != 2)
1435    throw usage(name);
1436
1437  transaction_guard guard(app.db);
1438  packet_writer pw(cout);
1439
1440  manifest_id m_old_id, m_new_id;
1441  manifest_data m_old_data, m_new_data;
1442  manifest_map m_old, m_new;
1443  patch_set ps;
1444  m_old_id = hexenc<id>(args[0]);
1445  m_new_id = hexenc<id>(args[1]);
1446  app.db.get_manifest_version(m_old_id, m_old_data);
1447  app.db.get_manifest_version(m_new_id, m_new_data);
1448  read_manifest_map(m_old_data, m_old);
1449  read_manifest_map(m_new_data, m_new);
1450  manifests_to_patch_set(m_old, m_new, app, ps);
1451  patch_set_to_packets(ps, app, pw);
1452  guard.commit();
1453}
1454
1455CMD(fdelta, "packet i/o", "<oldid> <newid>", "write file delta packet to stdout")
1456{
1457  if (args.size() != 2)
1458    throw usage(name);
1459
1460  transaction_guard guard(app.db);
1461  packet_writer pw(cout);
1462
1463  file_id f_old_id, f_new_id;
1464  file_data f_old_data, f_new_data;
1465  f_old_id = hexenc<id>(args[0]);
1466  f_new_id = hexenc<id>(args[1]);
1467  app.db.get_file_version(f_old_id, f_old_data);
1468  app.db.get_file_version(f_new_id, f_new_data);
1469  base64< gzip<delta> > del;
1470  diff(f_old_data.inner(), f_new_data.inner(), del);
1471  pw.consume_file_delta(f_old_id, f_new_id, file_delta(del));
1472  guard.commit();
1473}
1474
1475CMD(mdata, "packet i/o", "<id>", "write manifest data packet to stdout")
1476{
1477  if (args.size() != 1)
1478    throw usage(name);
1479
1480  transaction_guard guard(app.db);
1481  packet_writer pw(cout);
1482
1483  manifest_id m_id;
1484  manifest_data m_data;
1485  m_id = hexenc<id>(args[0]);
1486  app.db.get_manifest_version(m_id, m_data);
1487  pw.consume_manifest_data(m_id, m_data);
1488  guard.commit();
1489}
1490
1491
1492CMD(fdata, "packet i/o", "<id>", "write file data packet to stdout")
1493{
1494  if (args.size() != 1)
1495    throw usage(name);
1496
1497  transaction_guard guard(app.db);
1498  packet_writer pw(cout);
1499
1500  file_id f_id;
1501  file_data f_data;
1502  f_id = hexenc<id>(args[0]);
1503  app.db.get_file_version(f_id, f_data);
1504  pw.consume_file_data(f_id, f_data);
1505  guard.commit();
1506}
1507
1508CMD(mcerts, "packet i/o", "<id>", "write manifest cert packets to stdout")
1509{
1510  if (args.size() != 1)
1511    throw usage(name);
1512
1513  transaction_guard guard(app.db);
1514  packet_writer pw(cout);
1515
1516  manifest_id m_id;
1517  vector< manifest<cert> > certs;
1518
1519  m_id = hexenc<id>(args[0]);
1520  app.db.get_manifest_certs(m_id, certs);
1521  for (size_t i = 0; i < certs.size(); ++i)
1522    pw.consume_manifest_cert(certs[i]);
1523  guard.commit();
1524}
1525
1526CMD(fcerts, "packet i/o", "<id>", "write file cert packets to stdout")
1527{
1528  if (args.size() != 1)
1529    throw usage(name);
1530
1531  transaction_guard guard(app.db);
1532  packet_writer pw(cout);
1533
1534  file_id f_id;
1535  vector< file<cert> > certs;
1536
1537  f_id = hexenc<id>(args[0]);
1538  app.db.get_file_certs(f_id, certs);
1539  for (size_t i = 0; i < certs.size(); ++i)
1540    pw.consume_file_cert(certs[i]);
1541  guard.commit();
1542}
1543
1544CMD(pubkey, "packet i/o", "<id>", "write public key packet to stdout")
1545{
1546  if (args.size() != 1)
1547    throw usage(name);
1548
1549  transaction_guard guard(app.db);
1550  packet_writer pw(cout);
1551  rsa_keypair_id ident(args[0]);
1552  base64< rsa_pub_key > key;
1553  app.db.get_key(ident, key);
1554  pw.consume_public_key(ident, key);
1555  guard.commit();
1556}
1557
1558CMD(privkey, "packet i/o", "<id>", "write private key packet to stdout")
1559{
1560  if (args.size() != 1)
1561    throw usage(name);
1562
1563  transaction_guard guard(app.db);
1564  packet_writer pw(cout);
1565  rsa_keypair_id ident(args[0]);
1566  base64< arc4<rsa_priv_key> > key;
1567  app.db.get_key(ident, key);
1568  pw.consume_private_key(ident, key);
1569  guard.commit();
1570}
1571
1572
1573CMD(read, "packet i/o", "", "read packets from stdin")
1574{
1575  transaction_guard guard(app.db);
1576  packet_db_writer dbw(app, true);
1577  size_t count = read_packets(cin, dbw);
1578  N(count != 0, "no packets found on stdin");
1579  if (count == 1)
1580    P("read 1 packet\n");
1581  else
1582    P("read %d packets\n", count);
1583  guard.commit();
1584}
1585
1586
1587CMD(agraph, "graph visualization", "", "dump ancestry graph to stdout")
1588{
1589  vector< manifest<cert> > certs;
1590  transaction_guard guard(app.db);
1591  app.db.get_manifest_certs(ancestor_cert_name, certs);
1592  set<string> nodes;
1593  vector< pair<string, string> > edges;
1594  for(vector< manifest<cert> >::iterator i = certs.begin();
1595      i != certs.end(); ++i)
1596    {
1597      cert_value tv;
1598      decode_base64(i->inner().value, tv);
1599      nodes.insert(tv());
1600      nodes.insert(i->inner().ident());
1601      edges.push_back(make_pair(tv(), i->inner().ident()));
1602    }
1603  cout << "graph: " << endl << "{" << endl; // open graph
1604  for (set<string>::iterator i = nodes.begin(); i != nodes.end();
1605       ++i)
1606    {
1607      cout << "node: { title : \"" << *i << "\"}" << endl;
1608    }
1609  for (vector< pair<string,string> >::iterator i = edges.begin(); i != edges.end();
1610       ++i)
1611    {
1612      cout << "edge: { sourcename : \"" << i->first << "\"" << endl
1613	   << "        targetname : \"" << i->second << "\" }" << endl;
1614    }
1615  cout << "}" << endl << endl; // close graph
1616  guard.commit();
1617}
1618
1619CMD(fetch, "network", "[URL] [groupname]", "fetch recent changes from network")
1620{
1621  if (args.size() > 2)
1622    throw usage(name);
1623
1624  vector< pair<url,group> > sources;
1625
1626  if (args.size() == 0)
1627    {
1628      if (app.branch_name == "")
1629	{
1630	  P("no branch name provided, fetching from all known URLs\n");
1631	  app.db.get_all_known_sources(sources);
1632	}
1633      else
1634	{
1635	  N(app.lua.hook_get_fetch_sources(app.branch_name, sources),
1636	    ("no URL / group pairs found for branch " + app.branch_name));
1637	}
1638    }
1639  else
1640    {
1641      N(args.size() == 2, "need URL and groupname");
1642      sources.push_back(make_pair(url(args[0]),
1643				  group(args[1])));
1644    }
1645
1646  fetch_queued_blobs_from_network(sources, app);
1647}
1648
1649CMD(post, "network", "[URL] [groupname]", "post queued changes to network")
1650{
1651  if (args.size() > 2)
1652    throw usage(name);
1653
1654  vector< pair<url,group> > targets;
1655  if (args.size() == 0)
1656    {
1657      if (app.branch_name == "")
1658	{
1659	  P("no branch name provided, posting all queued targets\n");
1660	  app.db.get_queued_targets(targets);
1661	}
1662      else
1663	{
1664	  N(app.lua.hook_get_post_targets(app.branch_name, targets),
1665	    ("no URL / group pairs found for branch " + app.branch_name));
1666	}
1667    }
1668  else
1669    {
1670      N(args.size() == 2, "need URL and groupname");
1671      targets.push_back(make_pair(url(args[0]),
1672				  group(args[1])));
1673    }
1674
1675  post_queued_blobs_to_network(targets, app);
1676}
1677
1678
1679CMD(rcs_import, "rcs", "<rcsfile> ...", "import all versions in RCS files")
1680{
1681  if (args.size() < 1)
1682    throw usage(name);
1683
1684  transaction_guard guard(app.db);
1685  for (vector<string>::const_iterator i = args.begin();
1686       i != args.end(); ++i)
1687    {
1688      import_rcs_file(fs::path(*i), app.db);
1689    }
1690  guard.commit();
1691}
1692
1693
1694CMD(cvs_import, "rcs", "<cvsroot>", "import all versions in CVS repository")
1695{
1696  if (args.size() != 1)
1697    throw usage(name);
1698
1699  import_cvs_repo(fs::path(args.at(0)), app);
1700}
1701
1702
1703}; // namespace commands
1704