monotone

monotone Mtn Source Tree

Root/cmd_netsync.cc

1#include "base.hh"
2#include "cmd.hh"
3
4#include "diff_patch.hh"
5#include "netcmd.hh"
6#include "globish.hh"
7#include "keys.hh"
8#include "cert.hh"
9#include "revision.hh"
10#include "ui.hh"
11#include "uri.hh"
12#include "vocab_cast.hh"
13#include "platform-wrapped.hh"
14#include "app_state.hh"
15
16#include <fstream>
17
18using std::ifstream;
19using std::ofstream;
20using std::map;
21using std::set;
22using std::string;
23using std::vector;
24
25static const var_key default_server_key(var_domain("database"),
26 var_name("default-server"));
27static const var_key default_include_pattern_key(var_domain("database"),
28 var_name("default-include-pattern"));
29static const var_key default_exclude_pattern_key(var_domain("database"),
30 var_name("default-exclude-pattern"));
31
32static char const ws_internal_db_file_name[] = "mtn.db";
33
34static void
35extract_address(args_vector const & args,
36 arg_type & addr,
37 app_state & app)
38{
39 if (args.size() >= 1)
40 {
41 addr = idx(args, 0);
42 if (!app.db.var_exists(default_server_key) || app.opts.set_default)
43 {
44 P(F("setting default server to %s") % addr());
45 app.db.set_var(default_server_key, var_value(addr()));
46 }
47 }
48 else
49 {
50 N(app.db.var_exists(default_server_key),
51 F("no server given and no default server set"));
52 var_value addr_value;
53 app.db.get_var(default_server_key, addr_value);
54 addr = arg_type(addr_value());
55 L(FL("using default server address: %s") % addr());
56 }
57}
58
59static void
60find_key(utf8 const & addr,
61 globish const & include,
62 globish const & exclude,
63 app_state & app,
64 bool needed = true)
65{
66 if (app.opts.signing_key() != "")
67 return;
68
69 rsa_keypair_id key;
70 if (!app.lua.hook_get_netsync_key(app.opts.bind_address,
71 include, exclude,
72 key)
73 || key() == "")
74 {
75 if (needed)
76 {
77 get_user_key(key, app);
78 }
79 }
80 app.opts.signing_key = key;
81}
82
83static void
84find_key_if_needed(utf8 const & addr,
85 globish const & include,
86 globish const & exclude,
87 app_state & app,
88 bool needed = true)
89{
90 uri u;
91 bool transport_requires_auth(true);
92 if (parse_uri(addr(), u))
93 {
94 transport_requires_auth = app.lua.hook_use_transport_auth(u);
95 }
96 if (transport_requires_auth)
97 {
98 find_key(addr, include, exclude, app, needed);
99 }
100}
101
102static void
103extract_patterns(args_vector const & args,
104 globish & include_pattern, globish & exclude_pattern,
105 app_state & app)
106{
107 if (args.size() >= 2 || app.opts.exclude_given)
108 {
109 E(args.size() >= 2, F("no branch pattern given"));
110 int pattern_offset = 1;
111 vector<globish> patterns;
112 std::transform(args.begin() + pattern_offset, args.end(),
113 std::inserter(patterns, patterns.end()),
114 &typecast_vocab<utf8, globish>);
115 combine_and_check_globish(patterns, include_pattern);
116 vector<globish> excludes;
117 typecast_vocab_container(app.opts.exclude_patterns, excludes);
118 combine_and_check_globish(excludes, exclude_pattern);
119 if (!app.db.var_exists(default_include_pattern_key)
120 || app.opts.set_default)
121 {
122 P(F("setting default branch include pattern to '%s'") % include_pattern);
123 app.db.set_var(default_include_pattern_key, var_value(include_pattern()));
124 }
125 if (!app.db.var_exists(default_exclude_pattern_key)
126 || app.opts.set_default)
127 {
128 P(F("setting default branch exclude pattern to '%s'") % exclude_pattern);
129 app.db.set_var(default_exclude_pattern_key, var_value(exclude_pattern()));
130 }
131 }
132 else
133 {
134 N(app.db.var_exists(default_include_pattern_key),
135 F("no branch pattern given and no default pattern set"));
136 var_value pattern_value;
137 app.db.get_var(default_include_pattern_key, pattern_value);
138 include_pattern = globish(pattern_value());
139 L(FL("using default branch include pattern: '%s'") % include_pattern);
140 if (app.db.var_exists(default_exclude_pattern_key))
141 {
142 app.db.get_var(default_exclude_pattern_key, pattern_value);
143 exclude_pattern = globish(pattern_value());
144 }
145 else
146 exclude_pattern = globish();
147 L(FL("excluding: %s") % exclude_pattern);
148 }
149}
150
151CMD(push, "push", "", CMD_REF(network),
152 N_("[ADDRESS[:PORTNUMBER] [PATTERN ...]]"),
153 N_("Pushes branches to a netsync server"),
154 N_("This will push all branches that match the pattern given in PATTERN "
155 "to the netsync server at the address ADDRESS."),
156 options::opts::set_default | options::opts::exclude |
157 options::opts::key_to_push)
158{
159 arg_type addr;
160 globish include_pattern, exclude_pattern;
161 extract_address(args, addr, app);
162 extract_patterns(args, include_pattern, exclude_pattern, app);
163 find_key_if_needed(addr, include_pattern, exclude_pattern, app);
164
165 run_netsync_protocol(client_voice, source_role, addr,
166 include_pattern, exclude_pattern, app);
167}
168
169CMD(pull, "pull", "", CMD_REF(network),
170 N_("[ADDRESS[:PORTNUMBER] [PATTERN ...]]"),
171 N_("Pulls branches from a netsync server"),
172 N_("This pulls all branches that match the pattern given in PATTERN "
173 "from the netsync server at the address ADDRESS."),
174 options::opts::set_default | options::opts::exclude)
175{
176 arg_type addr;
177 globish include_pattern, exclude_pattern;
178 extract_address(args, addr, app);
179 extract_patterns(args, include_pattern, exclude_pattern, app);
180 find_key_if_needed(addr, include_pattern, exclude_pattern, app, false);
181
182 if (app.opts.signing_key() == "")
183 P(F("doing anonymous pull; use -kKEYNAME if you need authentication"));
184
185 run_netsync_protocol(client_voice, sink_role, addr,
186 include_pattern, exclude_pattern, app);
187}
188
189CMD(sync, "sync", "", CMD_REF(network),
190 N_("[ADDRESS[:PORTNUMBER] [PATTERN ...]]"),
191 N_("Synchronizes branches with a netsync server"),
192 N_("This synchronizes branches that match the pattern given in PATTERN "
193 "with the netsync server at the address ADDRESS."),
194 options::opts::set_default | options::opts::exclude |
195 options::opts::key_to_push)
196{
197 arg_type addr;
198 globish include_pattern, exclude_pattern;
199 extract_address(args, addr, app);
200 extract_patterns(args, include_pattern, exclude_pattern, app);
201 find_key_if_needed(addr, include_pattern, exclude_pattern, app);
202
203 run_netsync_protocol(client_voice, source_and_sink_role, addr,
204 include_pattern, exclude_pattern, app);
205}
206
207class dir_cleanup_helper
208{
209public:
210 dir_cleanup_helper(system_path const & new_dir, bool i_db) :
211 commited(false), internal_db(i_db), dir(new_dir) {}
212 ~dir_cleanup_helper()
213 {
214 if (!commited && directory_exists(dir))
215 {
216#ifdef WIN32
217 if (!internal_db)
218 delete_dir_recursive(dir);
219#else
220 delete_dir_recursive(dir);
221#endif /* WIN32 */
222 }
223 }
224 void commit(void)
225 {
226 commited = true;
227 }
228private:
229 bool commited;
230 bool internal_db;
231 system_path dir;
232};
233
234CMD(clone, "clone", "", CMD_REF(network),
235 N_("ADDRESS[:PORTNUMBER] [DIRECTORY]"),
236 N_("Checks out a revision from a remote database into a directory"),
237 N_("If a revision is given, that's the one that will be checked out. "
238 "Otherwise, it will be the head of the branch supplied. "
239 "If no directory is given, the branch name will be used as directory"),
240 options::opts::exclude | options::opts::branch | options::opts::revision)
241{
242 if (args.size() < 1 || args.size() > 2 || app.opts.revision_selectors.size() > 1)
243 throw usage(execid);
244
245 revision_id ident;
246 system_path workspace_dir;
247 utf8 addr = idx(args, 0);
248
249 N(app.opts.branch_given && !app.opts.branchname().empty(),
250 F("you must specify a branch to clone"));
251
252 if (args.size() == 1)
253 {
254 // No checkout dir specified, use branch name for dir.
255 workspace_dir = system_path(app.opts.branchname());
256 }
257 else
258 {
259 // Checkout to specified dir.
260 workspace_dir = system_path(idx(args, 1));
261 }
262
263 require_path_is_nonexistent
264 (workspace_dir, F("clone destination directory '%s' already exists") % workspace_dir);
265
266 // remember the initial working dir so that relative file:// db URIs will work
267 system_path start_dir(get_current_working_dir());
268
269 bool internal_db = !app.opts.dbname_given || app.opts.dbname.empty();
270
271 dir_cleanup_helper remove_on_fail(workspace_dir, internal_db);
272 app.create_workspace(workspace_dir);
273
274 if (internal_db)
275 app.set_database(system_path(bookkeeping_root / ws_internal_db_file_name));
276 else
277 app.set_database(app.opts.dbname);
278
279 if (get_path_status(app.db.get_filename()) == path::nonexistent)
280 app.db.initialize();
281
282 app.db.ensure_open();
283
284 if (!app.db.var_exists(default_server_key) || app.opts.set_default)
285 {
286 P(F("setting default server to %s") % addr);
287 app.db.set_var(default_server_key, var_value(addr()));
288 }
289
290 globish include_pattern(app.opts.branchname());
291
292 globish exclude_pattern;
293 {
294 vector<globish> excludes;
295 typecast_vocab_container(app.opts.exclude_patterns, excludes);
296 combine_and_check_globish(excludes, exclude_pattern);
297 }
298
299 find_key_if_needed(addr, include_pattern, exclude_pattern,
300 app, false);
301
302 if (app.opts.signing_key() == "")
303 P(F("doing anonymous pull; use -kKEYNAME if you need authentication"));
304
305 if (!app.db.var_exists(default_include_pattern_key)
306 || app.opts.set_default)
307 {
308 P(F("setting default branch include pattern to '%s'") % include_pattern);
309 app.db.set_var(default_include_pattern_key, var_value(include_pattern()));
310 }
311
312 if (app.opts.exclude_given)
313 {
314 if (!app.db.var_exists(default_exclude_pattern_key)
315 || app.opts.set_default)
316 {
317 P(F("setting default branch exclude pattern to '%s'") % exclude_pattern);
318 app.db.set_var(default_exclude_pattern_key, var_value(exclude_pattern()));
319 }
320 }
321
322 // make sure we're back in the original dir so that file: URIs work
323 change_current_working_dir(start_dir);
324
325 run_netsync_protocol(client_voice, sink_role, addr,
326 include_pattern, exclude_pattern, app);
327
328 change_current_working_dir(workspace_dir);
329
330 transaction_guard guard(app.db, false);
331
332 if (app.opts.revision_selectors.size() == 0)
333 {
334 // use branch head revision
335 N(!app.opts.branchname().empty(),
336 F("use --revision or --branch to specify what to checkout"));
337
338 set<revision_id> heads;
339 app.get_project().get_branch_heads(app.opts.branchname, heads);
340 N(heads.size() > 0,
341 F("branch '%s' is empty") % app.opts.branchname);
342 if (heads.size() > 1)
343 {
344 P(F("branch %s has multiple heads:") % app.opts.branchname);
345 for (set<revision_id>::const_iterator i = heads.begin(); i != heads.end(); ++i)
346 P(i18n_format(" %s") % describe_revision(app, *i));
347 P(F("choose one with '%s checkout -r<id>'") % ui.prog_name);
348 E(false, F("branch %s has multiple heads") % app.opts.branchname);
349 }
350 ident = *(heads.begin());
351 }
352 else if (app.opts.revision_selectors.size() == 1)
353 {
354 // use specified revision
355 complete(app, idx(app.opts.revision_selectors, 0)(), ident);
356 N(app.db.revision_exists(ident),
357 F("no such revision '%s'") % ident);
358
359 guess_branch(ident, app);
360
361 I(!app.opts.branchname().empty());
362
363 N(app.get_project().revision_is_in_branch(ident, app.opts.branchname),
364 F("revision %s is not a member of branch %s")
365 % ident % app.opts.branchname);
366 }
367
368 shared_ptr<roster_t> empty_roster = shared_ptr<roster_t>(new roster_t());
369 roster_t current_roster;
370
371 L(FL("checking out revision %s to directory %s") % ident % workspace_dir);
372 app.db.get_roster(ident, current_roster);
373
374 revision_t workrev;
375 make_revision_for_workspace(ident, cset(), workrev);
376 app.work.put_work_rev(workrev);
377
378 cset checkout;
379 make_cset(*empty_roster, current_roster, checkout);
380
381 map<file_id, file_path> paths;
382 get_content_paths(*empty_roster, paths);
383
384 content_merge_workspace_adaptor wca(app, empty_roster, paths);
385
386 app.work.perform_content_update(checkout, wca, false);
387
388 app.work.update_any_attrs();
389 app.work.maybe_update_inodeprints();
390 guard.commit();
391 remove_on_fail.commit();
392}
393
394struct pid_file
395{
396 explicit pid_file(system_path const & p)
397 : path(p)
398 {
399 if (path.empty())
400 return;
401 require_path_is_nonexistent(path, F("pid file '%s' already exists") % path);
402 file.open(path.as_external().c_str());
403 E(file.is_open(), F("failed to create pid file '%s'") % path);
404 file << get_process_id() << '\n';
405 file.flush();
406 }
407
408 ~pid_file()
409 {
410 if (path.empty())
411 return;
412 pid_t pid;
413 ifstream(path.as_external().c_str()) >> pid;
414 if (pid == get_process_id()) {
415 file.close();
416 delete_file(path);
417 }
418 }
419
420private:
421 ofstream file;
422 system_path path;
423};
424
425CMD_NO_WORKSPACE(serve, "serve", "", CMD_REF(network), "",
426 N_("Serves the database to connecting clients"),
427 "",
428 options::opts::bind | options::opts::pidfile |
429 options::opts::bind_stdio | options::opts::no_transport_auth)
430{
431 if (!args.empty())
432 throw usage(execid);
433
434 pid_file pid(app.opts.pidfile);
435
436 if (app.opts.use_transport_auth)
437 {
438 find_key(app.opts.bind_address, globish("*"), globish(""), app);
439
440 N(app.lua.hook_persist_phrase_ok(),
441F("need permission to store persistent passphrase (see hook persist_phrase_ok())"));
442 require_password(app.opts.signing_key, app);
443 }
444 else
445 {
446 E(app.opts.bind_stdio,
447F("The --no-transport-auth option is only permitted in combination with --stdio"));
448 }
449
450 app.db.ensure_open();
451
452 run_netsync_protocol(server_voice, source_and_sink_role, app.opts.bind_address,
453 globish("*"), globish(""), app);
454}
455
456// Local Variables:
457// mode: C++
458// fill-column: 76
459// c-file-style: "gnu"
460// indent-tabs-mode: nil
461// End:
462// 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