monotone

monotone Mtn Source Tree

Root/src/cmd_netsync.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2// 2006 Timothy Brownawell <tbrownaw@gmail.com>
3// 2010 Stephen Leake <stephen_leake@stephe-leake.org>
4//
5// This program is made available under the GNU GPL version 2.0 or
6// greater. See the accompanying file COPYING for details.
7//
8// This program is distributed WITHOUT ANY WARRANTY; without even the
9// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
10// PURPOSE.
11
12#include "base.hh"
13#include "cmd.hh"
14
15#include "automate_ostream_demuxed.hh"
16#include "basic_io.hh"
17#include "merge_content.hh"
18#include "netsync.hh"
19#include "network/connection_info.hh"
20#include "file_io.hh"
21#include "globish.hh"
22#include "keys.hh"
23#include "key_store.hh"
24#include "cert.hh"
25#include "revision.hh"
26#include "uri.hh"
27#include "vocab_cast.hh"
28#include "platform-wrapped.hh"
29#include "app_state.hh"
30#include "maybe_workspace_updater.hh"
31#include "project.hh"
32#include "work.hh"
33#include "database.hh"
34#include "roster.hh"
35#include "ui.hh"
36
37#include <fstream>
38
39using std::ifstream;
40using std::ofstream;
41using std::map;
42using std::set;
43using std::string;
44using std::vector;
45
46using boost::shared_ptr;
47
48static void
49extract_client_connection_info(options & opts,
50 project_t & project,
51 key_store & keys,
52 lua_hooks & lua,
53 connection_type type,
54 args_vector const & args,
55 shared_conn_info & info,
56 key_requiredness_flag key_requiredness = key_required)
57{
58 if (opts.remote_stdio_host_given)
59 {
60 netsync_connection_info::setup_from_uri(opts, project.db, lua, type,
61 opts.remote_stdio_host, info);
62 }
63 else
64 {
65 if (args.size() == 1)
66 {
67 E(!opts.exclude_given, origin::user,
68 F("cannot use '--exclude' in URI mode"));
69
70 netsync_connection_info::setup_from_uri(opts, project.db, lua, type,
71 idx(args, 0), info);
72 }
73 else if (args.size() >= 2)
74 {
75 arg_type server = idx(args, 0);
76 vector<arg_type> include;
77 include.insert(include.begin(),
78 args.begin() + 1,
79 args.end());
80 vector<arg_type> exclude = opts.exclude;
81
82 netsync_connection_info::setup_from_server_and_pattern(opts, project.db,
83 lua, type, server,
84 include,
85 exclude,
86 info);
87 }
88 else
89 {
90 // if no argument has been given and the --remote_stdio_host
91 // option has been left out, try to load the database defaults
92 // at least
93 netsync_connection_info::setup_default(opts, project.db,
94 lua, type, info);
95 }
96 }
97
98 opts.no_transport_auth =
99 !lua.hook_use_transport_auth(info->client.get_uri());
100
101 if (!opts.no_transport_auth)
102 {
103 cache_netsync_key(opts, project, keys, lua, info, key_requiredness);
104 }
105}
106
107CMD_AUTOMATE_NO_STDIO(remote_stdio,
108 N_("[URI]\n[ADDRESS[:PORTNUMBER]]"),
109 N_("Opens an 'automate stdio' connection to a remote server"),
110 "",
111 options::opts::max_netsync_version |
112 options::opts::min_netsync_version |
113 options::opts::set_default)
114{
115 if (args.size() > 1)
116 throw usage(execid);
117
118 app.opts.non_interactive = true;
119
120 if (!app.opts.dbname_given)
121 {
122 W(F("no database given; assuming '%s' database. This means that\n"
123 "we can't verify the server key, because we have no record of\n"
124 "what it should be.")
125 % memory_db_identifier);
126 app.opts.dbname_type = memory_db;
127 app.opts.dbname_given = true;
128 }
129
130 database db(app);
131 key_store keys(app);
132 project_t project(db);
133
134 shared_conn_info info;
135 extract_client_connection_info(app.opts, project, keys, app.lua,
136 automate_connection, args, info);
137
138 info->client.set_input_stream(std::cin);
139 long packet_size = constants::default_stdio_packet_size;
140 if (app.opts.automate_stdio_size_given)
141 packet_size = app.opts.automate_stdio_size;
142 automate_ostream os(output, packet_size);
143 info->client.set_output_stream(os);
144
145 run_netsync_protocol(app, app.opts, app.lua, project, keys,
146 client_voice, source_and_sink_role, info,
147 connection_counts::create());
148}
149
150// shamelessly copied and adapted from option.cc
151static void
152parse_options_from_args(args_vector & args,
153 std::vector<std::pair<std::string, arg_type> > & opts)
154{
155 bool seen_dashdash = false;
156 for (args_vector::size_type i = 0; i < args.size(); )
157 {
158 string name;
159 arg_type arg;
160
161 if (idx(args,i)() == "--" || seen_dashdash)
162 {
163 if (!seen_dashdash)
164 {
165 seen_dashdash = true;
166 }
167 ++i;
168 continue;
169 }
170 else if (idx(args,i)().substr(0,2) == "--")
171 {
172 string::size_type equals = idx(args,i)().find('=');
173 bool has_arg;
174 if (equals == string::npos)
175 {
176 name = idx(args,i)().substr(2);
177 has_arg = false;
178 }
179 else
180 {
181 name = idx(args,i)().substr(2, equals-2);
182 has_arg = true;
183 }
184
185 if (has_arg)
186 {
187 arg = arg_type(idx(args,i)().substr(equals+1), origin::user);
188 }
189 }
190 else if (idx(args,i)().substr(0,1) == "-")
191 {
192 name = idx(args,i)().substr(1,1);
193 bool has_arg = idx(args,i)().size() > 2;
194
195 if (has_arg)
196 {
197 arg = arg_type(idx(args,i)().substr(2), origin::user);
198 }
199 }
200 else
201 {
202 ++i;
203 continue;
204 }
205
206 opts.push_back(std::pair<std::string, arg_type>(name, arg));
207 args.erase(args.begin() + i);
208 }
209}
210
211CMD_AUTOMATE_NO_STDIO(remote,
212 N_("COMMAND [ARGS]"),
213 N_("Executes COMMAND on a remote server"),
214 "",
215 options::opts::remote_stdio_host |
216 options::opts::max_netsync_version |
217 options::opts::min_netsync_version |
218 options::opts::set_default)
219{
220 E(args.size() >= 1, origin::user,
221 F("wrong argument count"));
222
223 if (!app.opts.dbname_given)
224 {
225 W(F("no database given; assuming '%s' database. This means that\n"
226 "we can't verify the server key, because we have no record of\n"
227 "what it should be.")
228 % memory_db_identifier);
229 app.opts.dbname_type = memory_db;
230 app.opts.dbname_given = true;
231 }
232
233 database db(app);
234 key_store keys(app);
235 project_t project(db);
236
237 shared_conn_info info;
238 extract_client_connection_info(app.opts, project, keys, app.lua,
239 automate_connection, args_vector(), info);
240
241 args_vector cleaned_args(args);
242 std::vector<std::pair<std::string, arg_type> > opts;
243 parse_options_from_args(cleaned_args, opts);
244
245 std::stringstream ss;
246 if (opts.size() > 0)
247 {
248 ss << 'o';
249 for (unsigned int i=0; i < opts.size(); ++i)
250 {
251 ss << opts.at(i).first.size() << ':' << opts.at(i).first;
252 ss << opts.at(i).second().size() << ':' << opts.at(i).second();
253 }
254 ss << 'e' << ' ';
255 }
256
257 ss << 'l';
258 for (args_vector::size_type i=0; i<cleaned_args.size(); ++i)
259 {
260 std::string arg = idx(cleaned_args, i)();
261 ss << arg.size() << ':' << arg;
262 }
263 ss << 'e';
264
265 L(FL("stdio input: %s") % ss.str());
266
267 long packet_size = constants::default_stdio_packet_size;
268 if (app.opts.automate_stdio_size_given)
269 packet_size = app.opts.automate_stdio_size;
270 automate_ostream_demuxed os(output, std::cerr, packet_size);
271
272 info->client.set_input_stream(ss);
273 info->client.set_output_stream(os);
274
275 run_netsync_protocol(app, app.opts, app.lua, project, keys,
276 client_voice, source_and_sink_role, info,
277 connection_counts::create());
278
279 E(os.get_error() == 0, origin::network,
280 F("received remote error code %d") % os.get_error());
281}
282
283static void
284print_dryrun_info_cmd(protocol_role role,
285 shared_conn_counts counts,
286 project_t & project)
287{
288 // print dryrun info for command line
289 if (role != source_role)
290 {
291 if (counts->keys_in.can_have_more_than_min)
292 {
293 std::cout << (F("would receive %d revisions, %d certs, and at least %d keys\n")
294 % counts->revs_in.min_count
295 % counts->certs_in.min_count
296 % counts->keys_in.min_count);
297 }
298 else
299 {
300 std::cout << (F("would receive %d revisions, %d certs, and %d keys\n")
301 % counts->revs_in.min_count
302 % counts->certs_in.min_count
303 % counts->keys_in.min_count);
304 }
305 }
306 if (role != sink_role)
307 {
308 std::cout << (F("would send %d certs and %d keys\n")
309 % counts->certs_out.min_count
310 % counts->keys_out.min_count);
311 std::cout <<
312 (FP("would send %d revisions\n", // 0 revisions; nothing following, so no trailing colon
313 "would send %d revisions:\n",
314 counts->revs_out.min_count + 1)
315 % counts->revs_out.min_count);
316 map<branch_name, int> branch_counts;
317 for (vector<revision_id>::const_iterator i = counts->revs_out.items.begin();
318 i != counts->revs_out.items.end(); ++i)
319 {
320 set<branch_name> my_branches;
321 project.get_revision_branches(*i, my_branches);
322 for(set<branch_name>::iterator b = my_branches.begin();
323 b != my_branches.end(); ++b)
324 {
325 ++branch_counts[*b];
326 }
327 }
328 for (map<branch_name, int>::iterator i = branch_counts.begin();
329 i != branch_counts.end(); ++i)
330 {
331 std::cout << (F("%9d in branch '%s'\n") % i->second % i->first);
332 }
333 }
334}
335
336namespace
337{
338 namespace syms
339 {
340 symbol const estimate("estimate");
341 symbol const key("key");
342 symbol const receive_cert("receive_cert");
343 symbol const receive_key("receive_key");
344 symbol const receive_revision("receive_revision");
345 symbol const revision("revision");
346 symbol const send_branch("send_branch");
347 symbol const send_cert("send_cert");
348 symbol const send_key("send_key");
349 symbol const send_revision("send_revision");
350 symbol const value("value");
351 }
352}
353
354static void
355print_dryrun_info_auto(protocol_role role,
356 shared_conn_counts counts,
357 project_t & project,
358 std::ostream & output)
359{
360 // print dry run info for automate session
361 basic_io::printer pr;
362 basic_io::stanza st;
363
364 if (role != source_role)
365 {
366 // sink or sink_and_source; print sink info
367
368 if (counts->keys_in.can_have_more_than_min)
369 {
370 st.push_symbol(syms::estimate);
371 }
372
373 st.push_str_pair(syms::receive_revision,
374 boost::lexical_cast<string>(counts->revs_in.min_count));
375 st.push_str_pair(syms::receive_cert,
376 boost::lexical_cast<string>(counts->certs_in.min_count));
377 st.push_str_pair(syms::receive_key,
378 boost::lexical_cast<string>(counts->keys_in.min_count));
379 }
380
381 if (role != sink_role)
382 {
383 // source or sink_and_source; print source info
384
385 st.push_str_pair(syms::send_revision,
386 boost::lexical_cast<string>(counts->revs_out.items.size()));
387 st.push_str_pair(syms::send_cert,
388 boost::lexical_cast<string>(counts->certs_out.min_count));
389 st.push_str_pair(syms::send_key,
390 boost::lexical_cast<string>(counts->keys_out.min_count));
391
392 // count revisions per branch
393 map<branch_name, int> branch_counts;
394 for (vector<revision_id>::const_iterator i = counts->revs_out.items.begin();
395 i != counts->revs_out.items.end(); ++i)
396 {
397 set<branch_name> my_branches;
398 project.get_revision_branches(*i, my_branches);
399 for(set<branch_name>::iterator b = my_branches.begin();
400 b != my_branches.end(); ++b)
401 {
402 ++branch_counts[*b];
403 }
404 }
405 for (map<branch_name, int>::iterator i = branch_counts.begin();
406 i != branch_counts.end(); ++i)
407 {
408 st.push_str_triple(syms::send_branch, i->first(), boost::lexical_cast<string>(i->second));
409 }
410 }
411
412 pr.print_stanza(st);
413 output.write(pr.buf.data(), pr.buf.size());
414}
415
416static void
417print_cert(bool send,
418 cert const & item,
419 basic_io::printer & pr)
420{
421 basic_io::stanza st;
422 if (send)
423 {
424 st.push_str_pair(syms::send_cert, item.name());
425 }
426 else
427 {
428 st.push_str_pair(syms::receive_cert, item.name());
429 }
430 st.push_str_pair(syms::value, item.value());
431 st.push_binary_pair(syms::key, item.key.inner());
432 st.push_binary_pair(syms::revision, item.ident.inner());
433 pr.print_stanza(st);
434}
435
436static void
437print_info_auto(protocol_role role,
438 shared_conn_counts counts,
439 project_t & project,
440 std::ostream & output)
441{
442 // print info for automate session
443 basic_io::printer pr;
444
445 if (role != source_role)
446 {
447 // sink or sink_and_source; print sink info
448
449 vector<cert> unattached_certs;
450 map<revision_id, vector<cert> > rev_certs;
451 sort_rev_order (counts->revs_in, counts->certs_in, unattached_certs, rev_certs);
452
453 if (unattached_certs.size() > 0)
454 {
455 for (vector<cert>::const_iterator i = unattached_certs.begin();
456 i != unattached_certs.end(); ++i)
457 {
458 print_cert(false, *i, pr);
459 }
460 }
461
462 if (rev_certs.size() > 0)
463 {
464 for (map<revision_id, vector<cert> >::const_iterator i = rev_certs.begin();
465 i != rev_certs.end(); ++i)
466 {
467 basic_io::stanza st;
468 st.push_binary_pair(syms::receive_revision, i->first.inner());
469 pr.print_stanza(st);
470
471 for (vector<cert>::const_iterator j = i->second.begin();
472 j != i->second.end(); ++j)
473 {
474 print_cert(false, *j, pr);
475 }
476 }
477 }
478
479 if (counts->keys_in.items.size() > 0)
480 {
481 basic_io::stanza st;
482 for (vector<key_id>::const_iterator i = counts->keys_in.items.begin();
483 i != counts->keys_in.items.end(); ++i)
484 {
485 st.push_binary_pair(syms::receive_key, i->inner());
486 }
487 pr.print_stanza(st);
488 }
489 }
490
491 if (role != sink_role)
492 {
493 // source or sink_and_source; print source info
494
495 vector<cert> unattached_certs;
496 map<revision_id, vector<cert> > rev_certs;
497 sort_rev_order (counts->revs_out, counts->certs_out, unattached_certs, rev_certs);
498
499 if (unattached_certs.size() > 0)
500 {
501 for (vector<cert>::const_iterator i = unattached_certs.begin();
502 i != unattached_certs.end(); ++i)
503 {
504 print_cert(true, *i, pr);
505 }
506 }
507
508 if (rev_certs.size() > 0)
509 {
510 for (map<revision_id, vector<cert> >::const_iterator i = rev_certs.begin();
511 i != rev_certs.end(); ++i)
512 {
513 basic_io::stanza st;
514 st.push_binary_pair(syms::send_revision, i->first.inner());
515 pr.print_stanza(st);
516
517 for (vector<cert>::const_iterator j = i->second.begin();
518 j != i->second.end(); ++j)
519 {
520 print_cert(true, *j, pr);
521 }
522 }
523 }
524
525 if (counts->keys_out.items.size() > 0)
526 {
527 basic_io::stanza st;
528 for (vector<key_id>::const_iterator i = counts->keys_out.items.begin();
529 i != counts->keys_out.items.end(); ++i)
530 {
531 st.push_binary_pair(syms::send_key, i->inner());
532 }
533 pr.print_stanza(st);
534 }
535 }
536
537 output.write(pr.buf.data(), pr.buf.size());
538}
539
540CMD(push, "push", "", CMD_REF(network),
541 N_("[URI]\n[ADDRESS[:PORTNUMBER] [PATTERN ...]]"),
542 N_("Pushes branches to a netsync server"),
543 N_("This will push all branches that match the pattern given in PATTERN "
544 "to the netsync server at the address ADDRESS."),
545 options::opts::max_netsync_version | options::opts::min_netsync_version |
546 options::opts::set_default | options::opts::exclude |
547 options::opts::keys_to_push | options::opts::dryrun)
548{
549 database db(app);
550 key_store keys(app);
551 project_t project(db);
552
553 shared_conn_info info;
554 extract_client_connection_info(app.opts, project, keys, app.lua,
555 netsync_connection, args, info);
556
557 shared_conn_counts counts = connection_counts::create();
558 run_netsync_protocol(app, app.opts, app.lua, project, keys,
559 client_voice, source_role, info, counts);
560 if (app.opts.dryrun)
561 print_dryrun_info_cmd(source_role, counts, project);
562}
563
564CMD_AUTOMATE(push, N_("[URI]\n[ADDRESS[:PORTNUMBER] [PATTERN ...]]"),
565 N_("Pushes branches to a netsync server"),
566 "",
567 options::opts::max_netsync_version |
568 options::opts::min_netsync_version |
569 options::opts::set_default | options::opts::exclude |
570 options::opts::keys_to_push | options::opts::dryrun)
571{
572 database db(app);
573 key_store keys(app);
574 project_t project(db);
575
576 shared_conn_info info;
577 extract_client_connection_info(app.opts, project, keys, app.lua,
578 netsync_connection, args, info);
579
580 shared_conn_counts counts = connection_counts::create();
581 run_netsync_protocol(app, app.opts, app.lua, project, keys,
582 client_voice, source_role, info, counts);
583 if (app.opts.dryrun)
584 print_dryrun_info_auto(source_role, counts, project, output);
585 else
586 print_info_auto(source_role, counts, project, output);
587}
588
589CMD(pull, "pull", "", CMD_REF(network),
590 N_("[URI]\n[ADDRESS[:PORTNUMBER] [PATTERN ...]]"),
591 N_("Pulls branches from a netsync server"),
592 N_("This pulls all branches that match the pattern given in PATTERN "
593 "from the netsync server at the address ADDRESS."),
594 options::opts::max_netsync_version | options::opts::min_netsync_version |
595 options::opts::set_default | options::opts::exclude |
596 options::opts::auto_update | options::opts::dryrun)
597{
598 database db(app);
599 key_store keys(app);
600 project_t project(db);
601
602 maybe_workspace_updater updater(app, project);
603
604 shared_conn_info info;
605 extract_client_connection_info(app.opts, project, keys, app.lua,
606 netsync_connection, args, info, key_optional);
607
608 if (!keys.have_signing_key())
609 P(F("doing anonymous pull; use -kKEYNAME if you need authentication"));
610
611 shared_conn_counts counts = connection_counts::create();
612 run_netsync_protocol(app, app.opts, app.lua, project, keys,
613 client_voice, sink_role, info, counts);
614
615 if (app.opts.dryrun)
616 {
617 print_dryrun_info_cmd(sink_role, counts, project);
618 }
619 else
620 {
621 updater.maybe_do_update();
622 }
623}
624
625CMD_AUTOMATE(pull, N_("[URI]\n[ADDRESS[:PORTNUMBER] [PATTERN ...]]"),
626 N_("Pulls branches from a netsync server"),
627 "",
628 options::opts::max_netsync_version |
629 options::opts::min_netsync_version |
630 options::opts::set_default | options::opts::exclude |
631 options::opts::dryrun)
632{
633 database db(app);
634 key_store keys(app);
635 project_t project(db);
636
637 shared_conn_info info;
638 extract_client_connection_info(app.opts, project, keys, app.lua,
639 netsync_connection, args, info, key_optional);
640
641 shared_conn_counts counts = connection_counts::create();
642 run_netsync_protocol(app, app.opts, app.lua, project, keys,
643 client_voice, sink_role, info, counts);
644 if (app.opts.dryrun)
645 print_dryrun_info_auto(sink_role, counts, project, output);
646 else
647 print_info_auto(sink_role, counts, project, output);
648}
649
650CMD(sync, "sync", "", CMD_REF(network),
651 N_("[URI]\n[ADDRESS[:PORTNUMBER] [PATTERN ...]]"),
652 N_("Synchronizes branches with a netsync server"),
653 N_("This synchronizes branches that match the pattern given in PATTERN "
654 "with the netsync server at the address ADDRESS."),
655 options::opts::max_netsync_version | options::opts::min_netsync_version |
656 options::opts::set_default | options::opts::exclude |
657 options::opts::keys_to_push | options::opts::auto_update |
658 options::opts::dryrun)
659{
660 database db(app);
661 key_store keys(app);
662 project_t project(db);
663
664 maybe_workspace_updater updater(app, project);
665
666 shared_conn_info info;
667 extract_client_connection_info(app.opts, project, keys, app.lua,
668 netsync_connection, args, info);
669
670 if (app.opts.set_default && workspace::found)
671 {
672 // Write workspace options, including key; this is the simplest way to
673 // fix a "found multiple keys" error reported by sync.
674 workspace::set_options(app.opts, app.lua);
675 }
676
677 shared_conn_counts counts = connection_counts::create();
678 run_netsync_protocol(app, app.opts, app.lua, project, keys,
679 client_voice, source_and_sink_role, info, counts);
680
681 if (app.opts.dryrun)
682 {
683 print_dryrun_info_cmd(source_and_sink_role, counts, project);
684 }
685 else
686 {
687 updater.maybe_do_update();
688 }
689}
690
691CMD_AUTOMATE(sync, N_("[URI]\n[ADDRESS[:PORTNUMBER] [PATTERN ...]]"),
692 N_("Synchronizes branches with a netsync server"),
693 "",
694 options::opts::max_netsync_version | options::opts::min_netsync_version |
695 options::opts::set_default | options::opts::exclude |
696 options::opts::keys_to_push | options::opts::dryrun)
697{
698 database db(app);
699 key_store keys(app);
700 project_t project(db);
701
702 shared_conn_info info;
703 extract_client_connection_info(app.opts, project, keys, app.lua,
704 netsync_connection, args, info);
705
706 if (app.opts.set_default && workspace::found)
707 {
708 // Write workspace options, including key; this is the simplest way to
709 // fix a "found multiple keys" error reported by sync.
710 workspace::set_options(app.opts, app.lua);
711 }
712
713 shared_conn_counts counts = connection_counts::create();
714 run_netsync_protocol(app, app.opts, app.lua, project, keys,
715 client_voice, source_and_sink_role, info, counts);
716 if (app.opts.dryrun)
717 print_dryrun_info_auto(source_and_sink_role, counts, project, output);
718 else
719 print_info_auto(source_and_sink_role, counts, project, output);
720}
721
722CMD_NO_WORKSPACE(clone, "clone", "", CMD_REF(network),
723 N_("URI [DIRECTORY]\nHOST[:PORTNUMBER] BRANCH [DIRECTORY]"),
724 N_("Checks out a revision from a remote database into a directory"),
725 N_("If a revision is given, that's the one that will be checked out. "
726 "Otherwise, it will be the head of the branch supplied. "
727 "If no directory is given, the branch name will be used as directory"),
728 options::opts::max_netsync_version | options::opts::min_netsync_version |
729 options::opts::revision | options::opts::branch)
730{
731
732 bool url_arg = (args.size() == 1 || args.size() == 2) &&
733 idx(args, 0)().find("://") != string::npos;
734
735 bool host_branch_arg = (args.size() == 2 || args.size() == 3) &&
736 idx(args, 0)().find("://") == string::npos;
737
738 bool no_ambigious_revision = app.opts.revision.size() < 2;
739
740 if (!(no_ambigious_revision && (url_arg || host_branch_arg)))
741 throw usage(execid);
742
743 E(url_arg || (host_branch_arg && !app.opts.branch_given), origin::user,
744 F("the '--branch' option is only valid with an URI to clone"));
745
746 // we create the database before anything else, but we
747 // do not clean newly created databases up if the clone fails
748 // (and I think this is correct, because if the pull fails later
749 // on due to some network error, the use does not have to start
750 // again from the beginning)
751 database_path_helper helper(app.lua);
752 helper.maybe_set_default_alias(app.opts);
753
754 database db(app);
755 project_t project(db);
756 key_store keys(app);
757
758 db.create_if_not_exists();
759 db.ensure_open();
760
761 shared_conn_info info;
762 arg_type server = idx(args, 0);
763 arg_type workspace_arg;
764
765 if (url_arg)
766 {
767 E(!app.opts.exclude_given, origin::user,
768 F("cannot use '--exclude' in URI mode"));
769
770 netsync_connection_info::setup_from_uri(app.opts, project.db, app.lua,
771 netsync_connection, server, info);
772 if (args.size() == 2)
773 workspace_arg = idx(args, 1);
774 }
775 else
776 {
777 vector<arg_type> include;
778 include.push_back(idx(args, 1));
779 netsync_connection_info::setup_from_server_and_pattern(app.opts, project.db,
780 app.lua,
781 netsync_connection,
782 server, include,
783 app.opts.exclude,
784 info);
785 if (args.size() == 3)
786 workspace_arg = idx(args, 2);
787 }
788
789 if (app.opts.branch().empty())
790 {
791 globish include_pattern = info->client.get_include_pattern();
792 E(!include_pattern().empty() && !include_pattern.contains_meta_chars(),
793 origin::user, F("you must specify an unambiguous branch to clone"));
794 app.opts.branch = branch_name(include_pattern.unescaped(), origin::user);
795 }
796
797 I(!app.opts.branch().empty());
798
799 app.opts.no_transport_auth =
800 !app.lua.hook_use_transport_auth(info->client.get_uri());
801
802 if (!app.opts.no_transport_auth)
803 {
804 cache_netsync_key(app.opts, project, keys, app.lua, info, key_optional);
805 }
806
807 bool target_is_current_dir = false;
808 system_path workspace_dir;
809 if (workspace_arg().empty())
810 {
811 // No checkout dir specified, use branch name for dir.
812 workspace_dir = system_path(app.opts.branch(), origin::user);
813 }
814 else
815 {
816 target_is_current_dir =
817 workspace_arg == utf8(".");
818 workspace_dir = system_path(workspace_arg);
819 }
820
821 if (!target_is_current_dir)
822 {
823 require_path_is_nonexistent
824 (workspace_dir,
825 F("clone destination directory '%s' already exists")
826 % workspace_dir);
827 }
828
829 system_path _MTN_dir = workspace_dir / path_component("_MTN");
830
831 require_path_is_nonexistent
832 (_MTN_dir, F("bookkeeping directory already exists in '%s'")
833 % workspace_dir);
834
835 directory_cleanup_helper remove_on_fail(
836 target_is_current_dir ? _MTN_dir : workspace_dir
837 );
838
839 // remember the initial working dir so that relative file://
840 // db URIs will work
841 system_path start_dir(get_current_working_dir(), origin::system);
842
843 workspace::create_workspace(app.opts, app.lua, workspace_dir);
844
845 if (!keys.have_signing_key())
846 P(F("doing anonymous pull; use -kKEYNAME if you need authentication"));
847
848 // make sure we're back in the original dir so that file: URIs work
849 change_current_working_dir(start_dir);
850
851 run_netsync_protocol(app, app.opts, app.lua, project, keys,
852 client_voice, sink_role, info,
853 connection_counts::create());
854
855 change_current_working_dir(workspace_dir);
856
857 transaction_guard guard(db, false);
858
859 revision_id ident;
860 if (app.opts.revision.empty())
861 {
862 set<revision_id> heads;
863 project.get_branch_heads(app.opts.branch, heads,
864 app.opts.ignore_suspend_certs);
865 E(!heads.empty(), origin::user,
866 F("branch '%s' is empty") % app.opts.branch);
867 if (heads.size() > 1)
868 {
869 P(F("branch '%s' has multiple heads:") % app.opts.branch);
870 for (set<revision_id>::const_iterator i = heads.begin(); i != heads.end(); ++i)
871 P(i18n_format(" %s")
872 % describe_revision(app.opts, app.lua, project, *i));
873 P(F("choose one with '%s clone -r<id> URI'") % prog_name);
874 E(false, origin::user, F("branch '%s' has multiple heads") % app.opts.branch);
875 }
876 ident = *(heads.begin());
877 }
878 else if (app.opts.revision.size() == 1)
879 {
880 // use specified revision
881 complete(app.opts, app.lua, project, idx(app.opts.revision, 0)(), ident);
882
883 E(project.revision_is_in_branch(ident, app.opts.branch),
884 origin::user,
885 F("revision %s is not a member of branch '%s'")
886 % ident % app.opts.branch);
887 }
888
889 roster_t empty_roster, current_roster;
890
891 L(FL("checking out revision %s to directory %s")
892 % ident % workspace_dir);
893 db.get_roster(ident, current_roster);
894
895 workspace work(app);
896 revision_t workrev;
897 make_revision_for_workspace(ident, cset(), workrev);
898 work.put_work_rev(workrev);
899
900 cset checkout;
901 make_cset(empty_roster, current_roster, checkout);
902
903 content_merge_checkout_adaptor wca(db);
904 work.perform_content_update(empty_roster, current_roster, checkout, wca, false);
905
906 work.maybe_update_inodeprints(db);
907 guard.commit();
908 remove_on_fail.commit();
909}
910
911struct pid_file
912{
913 explicit pid_file(system_path const & p)
914 : path(p)
915 {
916 if (path.empty())
917 return;
918 require_path_is_nonexistent(path, F("pid file '%s' already exists") % path);
919 file.open(path.as_external().c_str());
920 E(file.is_open(), origin::system, F("failed to create pid file '%s'") % path);
921 file << get_process_id() << '\n';
922 file.flush();
923 }
924
925 ~pid_file()
926 {
927 if (path.empty())
928 return;
929 pid_t pid;
930 ifstream(path.as_external().c_str()) >> pid;
931 if (pid == get_process_id()) {
932 file.close();
933 delete_file(path);
934 }
935 }
936
937private:
938 ofstream file;
939 system_path path;
940};
941
942CMD_NO_WORKSPACE(serve, "serve", "", CMD_REF(network), "",
943 N_("Serves the database to connecting clients"),
944 "",
945 options::opts::max_netsync_version |
946 options::opts::min_netsync_version |
947 options::opts::pidfile |
948 options::opts::bind_opts)
949{
950 if (!args.empty())
951 throw usage(execid);
952
953 database db(app);
954 key_store keys(app);
955 project_t project(db);
956 pid_file pid(app.opts.pidfile);
957
958 db.ensure_open();
959
960 shared_conn_info info;
961 netsync_connection_info::setup_for_serve(app.opts, project.db, app.lua, info);
962
963 if (!app.opts.no_transport_auth)
964 {
965 cache_netsync_key(app.opts, project, keys, app.lua, info, key_required);
966 }
967
968 run_netsync_protocol(app, app.opts, app.lua, project, keys,
969 server_voice, source_and_sink_role, info,
970 connection_counts::create());
971}
972
973// Local Variables:
974// mode: C++
975// fill-column: 76
976// c-file-style: "gnu"
977// indent-tabs-mode: nil
978// End:
979// 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