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