monotone

monotone Mtn Source Tree

Root/commands.cc

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

Archive Download this file

Branches

Tags

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