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

Archive Download this file

Branches

Tags

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