monotone

monotone Mtn Source Tree

Root/cmd_netsync.cc

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