monotone

monotone Mtn Source Tree

Root/src/project.cc

1// Copyright (C) 2007 Timothy Brownawell <tbrownaw@gmail.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include "base.hh"
11#include "vector.hh"
12
13#include "cert.hh"
14#include "database.hh"
15#include "date_format.hh"
16#include "project.hh"
17#include "revision.hh"
18#include "transforms.hh"
19#include "lua_hooks.hh"
20#include "key_store.hh"
21#include "keys.hh"
22#include "options.hh"
23#include "vocab_cast.hh"
24#include "simplestring_xform.hh"
25#include "lexical_cast.hh"
26
27using std::make_pair;
28using std::multimap;
29using std::pair;
30using std::set;
31using std::string;
32using std::vector;
33using boost::lexical_cast;
34
35bool
36operator<(key_identity_info const & left,
37 key_identity_info const & right)
38{
39 if (left.id < right.id)
40 return true;
41 else if (left.id != right.id)
42 return false;
43 else if (left.official_name < right.official_name)
44 return true;
45 else if (left.official_name != right.official_name)
46 return false;
47 else
48 return left.given_name < right.given_name;
49}
50std::ostream &
51operator<<(std::ostream & os,
52 key_identity_info const & identity)
53{
54 os<<"{id="<<identity.id<<"; given_name="<<identity.given_name
55 <<"; official_name="<<identity.official_name<<"}";
56 return os;
57}
58
59
60project_t::project_t(database & db)
61 : db(db)
62{}
63
64void
65project_t::get_branch_list(set<branch_name> & names,
66 bool check_heads) const
67{
68 if (indicator.outdated())
69 {
70 vector<string> got;
71 indicator = db.get_branches(got);
72 branches.clear();
73 multimap<revision_id, revision_id> inverse_graph_cache;
74
75 for (vector<string>::iterator i = got.begin();
76 i != got.end(); ++i)
77 {
78 // check that the branch has at least one non-suspended head
79 const branch_name branch(*i, origin::database);
80 set<revision_id> heads;
81
82 if (check_heads)
83 get_branch_heads(branch, heads, false, &inverse_graph_cache);
84
85 if (!check_heads || !heads.empty())
86 branches.insert(branch);
87 }
88 }
89
90 names = branches;
91}
92
93void
94project_t::get_branch_list(globish const & glob,
95 set<branch_name> & names,
96 bool check_heads) const
97{
98 vector<string> got;
99 db.get_branches(glob, got);
100 names.clear();
101 multimap<revision_id, revision_id> inverse_graph_cache;
102
103 for (vector<string>::iterator i = got.begin();
104 i != got.end(); ++i)
105 {
106 // check that the branch has at least one non-suspended head
107 const branch_name branch(*i, origin::database);
108 set<revision_id> heads;
109
110 if (check_heads)
111 get_branch_heads(branch, heads, false, &inverse_graph_cache);
112
113 if (!check_heads || !heads.empty())
114 names.insert(branch);
115 }
116}
117
118namespace
119{
120 struct not_in_branch : public is_failure
121 {
122 project_t const & project;
123 branch_name const & branch;
124 not_in_branch(project_t const & project,
125 branch_name const & branch)
126 : project(project), branch(branch)
127 {}
128 virtual bool operator()(revision_id const & rid)
129 {
130 vector<cert> certs;
131 project.db.get_revision_certs(rid,
132 cert_name(branch_cert_name),
133 typecast_vocab<cert_value>(branch),
134 certs);
135 project.db.erase_bogus_certs(project, certs);
136 return certs.empty();
137 }
138 };
139
140 struct suspended_in_branch : public is_failure
141 {
142 project_t const & project;
143 branch_name const & branch;
144 suspended_in_branch(project_t const & project,
145 branch_name const & branch)
146 : project(project), branch(branch)
147 {}
148 virtual bool operator()(revision_id const & rid)
149 {
150 vector<cert> certs;
151 project.db.get_revision_certs(rid,
152 cert_name(suspend_cert_name),
153 typecast_vocab<cert_value>(branch),
154 certs);
155 project.db.erase_bogus_certs(project, certs);
156 return !certs.empty();
157 }
158 };
159}
160
161void
162project_t::get_branch_heads(branch_name const & name,
163 set<revision_id> & heads,
164 bool ignore_suspend_certs,
165 multimap<revision_id, revision_id> * inverse_graph_cache_ptr) const
166{
167 pair<branch_name, suspended_indicator>
168 cache_index(name, ignore_suspend_certs);
169 pair<outdated_indicator, set<revision_id> > &
170 branch = branch_heads[cache_index];
171 outdated_indicator & indicator = branch.first;
172 set<revision_id> & my_heads = branch.second;
173 if (indicator.outdated())
174 {
175 L(FL("getting heads of branch %s") % name);
176
177 set<revision_id> leaves;
178 indicator = db.get_branch_leaves(typecast_vocab<cert_value>(name),
179 leaves);
180
181 not_in_branch p(*this, name);
182
183 bool have_failure = false;
184 for (set<revision_id>::iterator l = leaves.begin();
185 l != leaves.end(); ++l)
186 {
187 if (p(*l))
188 {
189 have_failure = true;
190 break;
191 }
192 }
193
194 if (!have_failure)
195 {
196 my_heads = leaves;
197 }
198 else
199 { // bah, do it the slow way
200 indicator = db.get_revisions_with_cert(cert_name(branch_cert_name),
201 typecast_vocab<cert_value>(name),
202 my_heads);
203 erase_ancestors_and_failures(db, my_heads, p,
204 inverse_graph_cache_ptr);
205
206 }
207
208 if (!ignore_suspend_certs)
209 {
210 suspended_in_branch s(*this, name);
211 set<revision_id>::iterator it = my_heads.begin();
212 while (it != my_heads.end())
213 if (s(*it))
214 my_heads.erase(it++);
215 else
216 it++;
217 }
218
219 L(FL("found heads of branch %s (%s heads)")
220 % name % my_heads.size());
221 }
222 heads = my_heads;
223}
224
225bool
226project_t::revision_is_in_branch(revision_id const & id,
227 branch_name const & branch) const
228{
229 vector<cert> certs;
230 db.get_revision_certs(id, branch_cert_name,
231 typecast_vocab<cert_value>(branch), certs);
232
233 int num = certs.size();
234
235 db.erase_bogus_certs(*this, certs);
236
237 L(FL("found %d (%d valid) %s branch certs on revision %s")
238 % num
239 % certs.size()
240 % branch
241 % id);
242
243 return !certs.empty();
244}
245
246void
247project_t::put_revision_in_branch(key_store & keys,
248 revision_id const & id,
249 branch_name const & branch)
250{
251 put_cert(keys, id, branch_cert_name, typecast_vocab<cert_value>(branch));
252}
253
254bool
255project_t::revision_is_suspended_in_branch(revision_id const & id,
256 branch_name const & branch) const
257{
258 vector<cert> certs;
259 db.get_revision_certs(id, suspend_cert_name,
260 typecast_vocab<cert_value>(branch), certs);
261
262 int num = certs.size();
263
264 db.erase_bogus_certs(*this, certs);
265
266 L(FL("found %d (%d valid) %s suspend certs on revision %s")
267 % num
268 % certs.size()
269 % branch
270 % id);
271
272 return !certs.empty();
273}
274
275void
276project_t::suspend_revision_in_branch(key_store & keys,
277 revision_id const & id,
278 branch_name const & branch)
279{
280 put_cert(keys, id, suspend_cert_name, typecast_vocab<cert_value>(branch));
281}
282
283
284outdated_indicator
285project_t::get_revision_cert_hashes(revision_id const & rid,
286 vector<id> & hashes) const
287{
288 return db.get_revision_certs(rid, hashes);
289}
290
291outdated_indicator
292project_t::get_revision_certs(revision_id const & id,
293 vector<cert> & certs) const
294{
295 return db.get_revision_certs(id, certs);
296}
297
298outdated_indicator
299project_t::get_revision_certs_by_name(revision_id const & id,
300 cert_name const & name,
301 vector<cert> & certs) const
302{
303 outdated_indicator i = db.get_revision_certs(id, name, certs);
304 db.erase_bogus_certs(*this, certs);
305 return i;
306}
307
308outdated_indicator
309project_t::get_revision_branches(revision_id const & id,
310 set<branch_name> & branches) const
311{
312 vector<cert> certs;
313 outdated_indicator i = get_revision_certs_by_name(id, branch_cert_name, certs);
314 branches.clear();
315 for (vector<cert>::const_iterator i = certs.begin();
316 i != certs.end(); ++i)
317 branches.insert(typecast_vocab<branch_name>(i->value));
318
319 return i;
320}
321
322outdated_indicator
323project_t::get_branch_certs(branch_name const & branch,
324 vector<pair<id, cert> > & certs) const
325{
326 return db.get_revision_certs(branch_cert_name,
327 typecast_vocab<cert_value>(branch), certs);
328}
329
330tag_t::tag_t(revision_id const & ident,
331 utf8 const & name,
332 key_id const & key)
333 : ident(ident), name(name), key(key)
334{}
335
336bool
337operator < (tag_t const & a, tag_t const & b)
338{
339 if (a.name < b.name)
340 return true;
341 else if (a.name == b.name)
342 {
343 if (a.ident < b.ident)
344 return true;
345 else if (a.ident == b.ident)
346 {
347 if (a.key < b.key)
348 return true;
349 }
350 }
351 return false;
352}
353
354outdated_indicator
355project_t::get_tags(set<tag_t> & tags) const
356{
357 vector<cert> certs;
358 outdated_indicator i = db.get_revision_certs(tag_cert_name, certs);
359 db.erase_bogus_certs(*this, certs);
360 tags.clear();
361 for (vector<cert>::const_iterator i = certs.begin();
362 i != certs.end(); ++i)
363 tags.insert(tag_t(revision_id(i->ident),
364 typecast_vocab<utf8>(i->value),
365 i->key));
366
367 return i;
368}
369
370void
371project_t::put_tag(key_store & keys,
372 revision_id const & id,
373 string const & name)
374{
375 put_cert(keys, id, tag_cert_name, cert_value(name, origin::user));
376}
377
378
379
380void
381project_t::put_standard_certs(key_store & keys,
382 revision_id const & id,
383 branch_name const & branch,
384 utf8 const & changelog,
385 date_t const & time,
386 string const & author)
387{
388 I(!branch().empty());
389 I(!changelog().empty());
390 I(time.valid());
391 I(!author.empty());
392
393 put_cert(keys, id, branch_cert_name,
394 typecast_vocab<cert_value>(branch));
395 put_cert(keys, id, changelog_cert_name,
396 typecast_vocab<cert_value>(changelog));
397 put_cert(keys, id, date_cert_name,
398 cert_value(time.as_iso_8601_extended(), origin::internal));
399 put_cert(keys, id, author_cert_name,
400 cert_value(author, origin::user));
401}
402
403void
404project_t::put_standard_certs_from_options(options const & opts,
405 lua_hooks & lua,
406 key_store & keys,
407 revision_id const & id,
408 branch_name const & branch,
409 utf8 const & changelog)
410{
411 date_t date;
412 if (opts.date_given)
413 date = opts.date;
414 else
415 date = date_t::now();
416
417 string author = opts.author();
418 if (author.empty())
419 {
420 key_identity_info key;
421 get_user_key(opts, lua, db, keys, *this, key.id);
422 complete_key_identity_from_id(lua, key);
423
424 if (!lua.hook_get_author(branch, key, author))
425 {
426 author = key.official_name();
427 }
428 }
429
430 put_standard_certs(keys, id, branch, changelog, date, author);
431}
432
433bool
434project_t::put_cert(key_store & keys,
435 revision_id const & id,
436 cert_name const & name,
437 cert_value const & value)
438{
439 I(keys.have_signing_key());
440
441 cert t(id, name, value, keys.signing_key);
442 string signed_text;
443 t.signable_text(signed_text);
444 load_key_pair(keys, t.key);
445 keys.make_signature(db, t.key, signed_text, t.sig);
446
447 cert cc(t);
448 return db.put_revision_cert(cc);
449}
450
451void
452project_t::put_revision_comment(key_store & keys,
453 revision_id const & id,
454 utf8 const & comment)
455{
456 put_cert(keys, id, comment_cert_name, typecast_vocab<cert_value>(comment));
457}
458
459void
460project_t::put_revision_testresult(key_store & keys,
461 revision_id const & id,
462 string const & results)
463{
464 bool passed;
465 if (lowercase(results) == "true" ||
466 lowercase(results) == "yes" ||
467 lowercase(results) == "pass" ||
468 results == "1")
469 passed = true;
470 else if (lowercase(results) == "false" ||
471 lowercase(results) == "no" ||
472 lowercase(results) == "fail" ||
473 results == "0")
474 passed = false;
475 else
476 E(false, origin::user,
477 F("could not interpret test result string '%s'; "
478 "valid strings are: 1, 0, yes, no, true, false, pass, fail")
479 % results);
480
481 put_cert(keys, id, testresult_cert_name,
482 cert_value(lexical_cast<string>(passed), origin::internal));
483}
484
485void
486project_t::lookup_key_by_name(key_store * const keys,
487 lua_hooks & lua,
488 key_name const & name,
489 key_id & id) const
490{
491 set<key_id> ks_match_by_local_name;
492 set<key_id> db_match_by_local_name;
493 set<key_id> ks_match_by_given_name;
494
495 if (keys)
496 {
497 vector<key_id> storekeys;
498 keys->get_key_ids(storekeys);
499 for (vector<key_id>::const_iterator i = storekeys.begin();
500 i != storekeys.end(); ++i)
501 {
502 key_name i_name;
503 keypair kp;
504 keys->get_key_pair(*i, i_name, kp);
505
506 if (i_name == name)
507 ks_match_by_given_name.insert(*i);
508
509 key_identity_info identity;
510 identity.id = *i;
511 identity.given_name = i_name;
512 if (lua.hook_get_local_key_name(identity))
513 {
514 if (identity.official_name == name)
515 ks_match_by_local_name.insert(*i);
516 }
517 }
518 }
519 if (db.database_specified())
520 {
521 vector<key_id> dbkeys;
522 db.get_key_ids(dbkeys);
523 for (vector<key_id>::const_iterator i = dbkeys.begin();
524 i != dbkeys.end(); ++i)
525 {
526 key_name i_name;
527 rsa_pub_key pub;
528 db.get_pubkey(*i, i_name, pub);
529
530 key_identity_info identity;
531 identity.id = *i;
532 identity.given_name = i_name;
533 if (lua.hook_get_local_key_name(identity))
534 {
535 if (identity.official_name == name)
536 db_match_by_local_name.insert(*i);
537 }
538 }
539 }
540
541 E(ks_match_by_local_name.size() < 2, origin::user,
542 F("you have %d keys named '%s'") %
543 ks_match_by_local_name.size() % name);
544 if (ks_match_by_local_name.size() == 1)
545 {
546 id = *ks_match_by_local_name.begin();
547 return;
548 }
549 E(db_match_by_local_name.size() < 2, origin::user,
550 F("there are %d keys named '%s'") %
551 db_match_by_local_name.size() % name);
552 if (db_match_by_local_name.size() == 1)
553 {
554 id = *db_match_by_local_name.begin();
555 return;
556 }
557 E(ks_match_by_given_name.size() < 2, origin::user,
558 F("you have %d keys named '%s'") %
559 ks_match_by_local_name.size() % name);
560 if (ks_match_by_given_name.size() == 1)
561 {
562 id = *ks_match_by_given_name.begin();
563 return;
564 }
565 E(false, origin::user,
566 F("there is no key named '%s'") % name);
567}
568
569void
570project_t::get_given_name_of_key(key_store * const keys,
571 key_id const & id,
572 key_name & name) const
573{
574 if (keys && keys->key_pair_exists(id))
575 {
576 keypair kp;
577 keys->get_key_pair(id, name, kp);
578 }
579 else if (db.database_specified() && db.public_key_exists(id))
580 {
581 rsa_pub_key pub;
582 db.get_pubkey(id, name, pub);
583 }
584 else
585 {
586 E(false, id.inner().made_from,
587 F("key %s does not exist") % id);
588 }
589}
590
591void
592project_t::complete_key_identity_from_id(key_store * const keys,
593 lua_hooks & lua,
594 key_identity_info & info) const
595{
596 MM(info.id);
597 MM(info.official_name);
598 MM(info.given_name);
599 I(!info.id.inner()().empty());
600 get_given_name_of_key(keys, info.id, info.given_name);
601 lua.hook_get_local_key_name(info);
602}
603
604void
605project_t::complete_key_identity_from_id(key_store & keys,
606 lua_hooks & lua,
607 key_identity_info & info) const
608{
609 complete_key_identity_from_id(&keys, lua, info);
610}
611
612void
613project_t::complete_key_identity_from_id(lua_hooks & lua,
614 key_identity_info & info) const
615{
616 complete_key_identity_from_id(0, lua, info);
617}
618
619void
620project_t::get_key_identity(key_store * const keys,
621 lua_hooks & lua,
622 external_key_name const & input,
623 key_identity_info & output) const
624{
625 try
626 {
627 string in2 = decode_hexenc(input(), origin::no_fault);
628 id ident(in2, origin::no_fault);
629 // set this separately so we can ensure that the key_id() calls
630 // above throw recoverable_failure instead of unrecoverable_failure
631 ident.made_from = input.made_from;
632 output.id = key_id(ident);
633 complete_key_identity_from_id(keys, lua, output);
634 return;
635 }
636 catch (recoverable_failure &)
637 {
638 output.official_name = typecast_vocab<key_name>(input);
639 lookup_key_by_name(keys, lua, output.official_name, output.id);
640 get_given_name_of_key(keys, output.id, output.given_name);
641 return;
642 }
643}
644
645void
646project_t::get_key_identity(key_store & keys,
647 lua_hooks & lua,
648 external_key_name const & input,
649 key_identity_info & output) const
650{
651 get_key_identity(&keys, lua, input, output);
652}
653
654void
655project_t::get_key_identity(lua_hooks & lua,
656 external_key_name const & input,
657 key_identity_info & output) const
658{
659 get_key_identity(0, lua, input, output);
660}
661
662
663// These should maybe be converted to member functions.
664
665string
666describe_revision(options const & opts, lua_hooks & lua,
667 project_t & project, revision_id const & id)
668{
669 cert_name author_name(author_cert_name);
670 cert_name date_name(date_cert_name);
671
672 string description;
673
674 description += encode_hexenc(id.inner()(), id.inner().made_from);
675
676 string date_fmt = get_date_format(opts, lua, date_time_short);
677
678 // append authors and date of this revision
679 vector<cert> certs;
680 project.get_revision_certs(id, certs);
681 string authors;
682 string dates;
683 for (vector<cert>::const_iterator i = certs.begin();
684 i != certs.end(); ++i)
685 {
686 if (i->name == author_cert_name)
687 {
688 authors += " ";
689 authors += i->value();
690 }
691 else if (i->name == date_cert_name)
692 {
693 dates += " ";
694 dates += date_t(i->value()).as_formatted_localtime(date_fmt);
695 }
696 }
697
698 description += authors;
699 description += dates;
700 return description;
701}
702
703void
704notify_if_multiple_heads(project_t & project,
705 branch_name const & branchname,
706 bool ignore_suspend_certs)
707{
708 set<revision_id> heads;
709 project.get_branch_heads(branchname, heads, ignore_suspend_certs);
710 if (heads.size() > 1) {
711 string prefixedline;
712 prefix_lines_with(_("note: "),
713 _("branch '%s' has multiple heads\n"
714 "perhaps consider '%s merge'"),
715 prefixedline);
716 P(i18n_format(prefixedline) % branchname % prog_name);
717 }
718}
719
720// Guess which branch is appropriate for a commit below IDENT.
721// OPTS may override. Branch name is returned in BRANCHNAME.
722// Does not modify branch state in OPTS.
723void
724guess_branch(options & opts, project_t & project,
725 revision_id const & ident, branch_name & branchname)
726{
727 if (opts.branch_given && !opts.branch().empty())
728 branchname = opts.branch;
729 else
730 {
731 E(!ident.inner()().empty(), origin::user,
732 F("no branch found for empty revision, "
733 "please provide a branch name"));
734
735 set<branch_name> branches;
736 project.get_revision_branches(ident, branches);
737
738 E(!branches.empty(), origin::user,
739 F("no branch certs found for revision %s, "
740 "please provide a branch name") % ident);
741
742 E(branches.size() == 1, origin::user,
743 F("multiple branch certs found for revision %s, "
744 "please provide a branch name") % ident);
745
746 set<branch_name>::iterator i = branches.begin();
747 I(i != branches.end());
748 branchname = *i;
749 }
750}
751
752// As above, but set the branch name in the options
753// if it wasn't already set.
754void
755guess_branch(options & opts, project_t & project, revision_id const & ident)
756{
757 branch_name branchname;
758 guess_branch(opts, project, ident, branchname);
759 opts.branch = branchname;
760}
761
762// Local Variables:
763// mode: C++
764// fill-column: 76
765// c-file-style: "gnu"
766// indent-tabs-mode: nil
767// End:
768// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

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