monotone

monotone Mtn Source Tree

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