monotone

monotone Mtn Source Tree

Root/selectors.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include "base.hh"
11#include "selectors.hh"
12#include "sanity.hh"
13#include "constants.hh"
14#include "database.hh"
15#include "app_state.hh"
16#include "project.hh"
17#include "globish.hh"
18#include "cmd.hh"
19#include "work.hh"
20#include "transforms.hh"
21
22#include <algorithm>
23#include <boost/tokenizer.hpp>
24
25using std::make_pair;
26using std::pair;
27using std::set;
28using std::string;
29using std::vector;
30using std::set_intersection;
31using std::inserter;
32
33enum selector_type
34 {
35 sel_author,
36 sel_branch,
37 sel_head,
38 sel_any_head,
39 sel_date,
40 sel_tag,
41 sel_ident,
42 sel_cert,
43 sel_earlier,
44 sel_later,
45 sel_parent,
46 sel_unknown
47 };
48
49typedef vector<pair<selector_type, string> > selector_list;
50
51static void
52decode_selector(project_t & project,
53 options const & opts,
54 lua_hooks & lua,
55 string const & orig_sel,
56 selector_type & type,
57 string & sel)
58{
59 sel = orig_sel;
60
61 L(FL("decoding selector '%s'") % sel);
62
63 string tmp;
64 if (sel.size() < 2 || sel[1] != ':')
65 {
66 if (!lua.hook_expand_selector(sel, tmp))
67 {
68 L(FL("expansion of selector '%s' failed") % sel);
69 }
70 else
71 {
72 P(F("expanded selector '%s' -> '%s'") % sel % tmp);
73 sel = tmp;
74 }
75 }
76
77 if (sel.size() >= 2 && sel[1] == ':')
78 {
79 switch (sel[0])
80 {
81 case 'a':
82 type = sel_author;
83 break;
84 case 'b':
85 type = sel_branch;
86 break;
87 case 'h':
88 type = opts.ignore_suspend_certs ? sel_any_head : sel_head;
89 break;
90 case 'd':
91 type = sel_date;
92 break;
93 case 'i':
94 type = sel_ident;
95 break;
96 case 't':
97 type = sel_tag;
98 break;
99 case 'c':
100 type = sel_cert;
101 break;
102 case 'l':
103 type = sel_later;
104 break;
105 case 'e':
106 type = sel_earlier;
107 break;
108 case 'p':
109 type = sel_parent;
110 break;
111 default:
112 W(F("unknown selector type: %c") % sel[0]);
113 break;
114 }
115 sel.erase(0,2);
116
117 // validate certain selector values and provide defaults
118 switch (type)
119 {
120 case sel_date:
121 case sel_later:
122 case sel_earlier:
123 if (lua.hook_exists("expand_date"))
124 {
125 N(lua.hook_expand_date(sel, tmp),
126 F("selector '%s' is not a valid date\n") % sel);
127 }
128 else
129 {
130 // if expand_date is not available, start with something
131 tmp = sel;
132 }
133
134 // if we still have a too short datetime string, expand it with
135 // default values, but only if the type is earlier or later;
136 // for searching a specific date cert this makes no sense
137 // FIXME: this is highly speculative if expand_date wasn't called
138 // beforehand - tmp could be _anything_ but a partial date string
139 if (tmp.size()<8 && (sel_later==type || sel_earlier==type))
140 tmp += "-01T00:00:00";
141 else if (tmp.size()<11 && (sel_later==type || sel_earlier==type))
142 tmp += "T00:00:00";
143 N(tmp.size()==19 || sel_date==type,
144 F("selector '%s' is not a valid date (%s)") % sel % tmp);
145
146 if (sel != tmp)
147 {
148 P (F ("expanded date '%s' -> '%s'\n") % sel % tmp);
149 sel = tmp;
150 }
151 if (sel_date == type && sel.size() < 19)
152 sel = string("*") + sel + "*"; // to be GLOBbed later
153 break;
154
155 case sel_branch:
156 case sel_head:
157 case sel_any_head:
158 if (sel.empty())
159 {
160 i18n_format msg = sel_branch == type
161 ? F("the empty branch selector b: refers to "
162 "the current branch")
163 : F("the empty head selector h: refers to "
164 "the head of the current branch");
165 workspace::require_workspace(msg);
166 sel = opts.branchname();
167 }
168 break;
169
170 case sel_cert:
171 N(!sel.empty(),
172 F("the cert selector c: may not be empty"));
173 break;
174
175 case sel_parent:
176 if (sel.empty())
177 {
178 workspace work(opts, lua, F("the empty parent selector p: refers to "
179 "the base revision of the workspace"));
180
181 parent_map parents;
182 set<revision_id> parent_ids;
183
184 work.get_parent_rosters(project.db, parents);
185
186 for (parent_map::const_iterator i = parents.begin();
187 i != parents.end(); ++i)
188 {
189 parent_ids.insert(i->first);
190 }
191
192 diagnose_ambiguous_expansion(project, "p:", parent_ids);
193 sel = (* parent_ids.begin()).inner()();
194 }
195 else
196 sel = decode_hexenc(sel);
197 break;
198 default: break;
199 }
200 }
201}
202
203static void
204parse_selector(project_t & project,
205 options const & opts,
206 lua_hooks & lua,
207 string const & str, selector_list & sels)
208{
209 sels.clear();
210
211 // this rule should always be enabled, even if the user specifies
212 // --norc: if you provide a revision id, you get a revision id.
213 if (str.find_first_not_of(constants::legal_id_bytes) == string::npos
214 && str.size() == constants::idlen)
215 {
216 sels.push_back(make_pair(sel_ident, str));
217 }
218 else
219 {
220 typedef boost::tokenizer<boost::escaped_list_separator<char> > tokenizer;
221 boost::escaped_list_separator<char> slash("\\", "/", "");
222 tokenizer tokens(str, slash);
223
224 vector<string> selector_strings;
225 copy(tokens.begin(), tokens.end(), back_inserter(selector_strings));
226
227 for (vector<string>::const_iterator i = selector_strings.begin();
228 i != selector_strings.end(); ++i)
229 {
230 string sel;
231 selector_type type = sel_unknown;
232
233 decode_selector(project, opts, lua, *i, type, sel);
234 sels.push_back(make_pair(type, sel));
235 }
236 }
237}
238
239static void
240complete_one_selector(project_t & project,
241 selector_type ty, string const & value,
242 set<revision_id> & completions)
243{
244 switch (ty)
245 {
246 case sel_ident:
247 project.db.complete(value, completions);
248 break;
249
250 case sel_parent:
251 I(!value.empty());
252 project.db.select_parent(value, completions);
253 break;
254
255 case sel_author:
256 project.db.select_cert(author_cert_name(), value, completions);
257 break;
258
259 case sel_tag:
260 project.db.select_cert(tag_cert_name(), value, completions);
261 break;
262
263 case sel_branch:
264 I(!value.empty());
265 project.db.select_cert(branch_cert_name(), value, completions);
266 break;
267
268 case sel_unknown:
269 project.db.select_author_tag_or_branch(value, completions);
270 break;
271
272 case sel_date:
273 project.db.select_date(value, "GLOB", completions);
274 break;
275
276 case sel_earlier:
277 project.db.select_date(value, "<=", completions);
278 break;
279
280 case sel_later:
281 project.db.select_date(value, ">", completions);
282 break;
283
284 case sel_cert:
285 {
286 I(!value.empty());
287 size_t spot = value.find("=");
288
289 if (spot != (size_t)-1)
290 {
291 string certname;
292 string certvalue;
293
294 certname = value.substr(0, spot);
295 spot++;
296 certvalue = value.substr(spot);
297
298 project.db.select_cert(certname, certvalue, completions);
299 }
300 else
301 project.db.select_cert(value, completions);
302 }
303 break;
304
305 case sel_head:
306 case sel_any_head:
307 {
308 // get branch names
309 set<branch_name> branch_names;
310 I(!value.empty());
311 project.get_branch_list(globish(value), branch_names);
312
313 L(FL("found %d matching branches") % branch_names.size());
314
315 // for each branch name, get the branch heads
316 for (set<branch_name>::const_iterator bn = branch_names.begin();
317 bn != branch_names.end(); bn++)
318 {
319 set<revision_id> branch_heads;
320 project.get_branch_heads(*bn, branch_heads, ty == sel_any_head);
321 completions.insert(branch_heads.begin(), branch_heads.end());
322 L(FL("after get_branch_heads for %s, heads has %d entries")
323 % (*bn) % completions.size());
324 }
325 }
326 break;
327 }
328}
329
330static void
331complete_selector(project_t & project,
332 selector_list const & limit,
333 set<revision_id> & completions)
334{
335 if (limit.empty()) // all the ids in the database
336 {
337 project.db.complete("", completions);
338 return;
339 }
340
341 selector_list::const_iterator i = limit.begin();
342 complete_one_selector(project, i->first, i->second, completions);
343 i++;
344
345 while (i != limit.end())
346 {
347 set<revision_id> candidates;
348 set<revision_id> intersection;
349 complete_one_selector(project, i->first, i->second, candidates);
350
351 intersection.clear();
352 set_intersection(completions.begin(), completions.end(),
353 candidates.begin(), candidates.end(),
354 inserter(intersection, intersection.end()));
355
356 completions = intersection;
357 i++;
358 }
359}
360
361void
362complete(options const & opts, lua_hooks & lua,
363 project_t & project,
364 string const & str,
365 set<revision_id> & completions)
366{
367 selector_list sels;
368 parse_selector(project, opts, lua, str, sels);
369
370 // avoid logging if there's no expansion to be done
371 if (sels.size() == 1
372 && sels[0].first == sel_ident
373 && sels[0].second.size() == constants::idlen)
374 {
375 completions.insert(revision_id(decode_hexenc(sels[0].second)));
376 N(project.db.revision_exists(*completions.begin()),
377 F("no such revision '%s'") % *completions.begin());
378 return;
379 }
380
381 P(F("expanding selection '%s'") % str);
382 complete_selector(project, sels, completions);
383
384 N(!completions.empty(),
385 F("no match for selection '%s'") % str);
386
387 for (set<revision_id>::const_iterator i = completions.begin();
388 i != completions.end(); ++i)
389 {
390 P(F("expanded to '%s'") % *i);
391
392 // This may be impossible, but let's make sure.
393 // All the callers used to do it.
394 N(project.db.revision_exists(*i),
395 F("no such revision '%s'") % *i);
396 }
397}
398
399void
400complete(options const & opts, lua_hooks & lua,
401 project_t & project,
402 string const & str,
403 revision_id & completion)
404{
405 set<revision_id> completions;
406
407 complete(opts, lua, project, str, completions);
408
409 I(!completions.empty());
410 diagnose_ambiguous_expansion(project, str, completions);
411
412 completion = *completions.begin();
413}
414
415
416void
417expand_selector(options const & opts, lua_hooks & lua,
418 project_t & project,
419 string const & str,
420 set<revision_id> & completions)
421{
422 selector_list sels;
423 parse_selector(project, opts, lua, str, sels);
424
425 // avoid logging if there's no expansion to be done
426 if (sels.size() == 1
427 && sels[0].first == sel_ident
428 && sels[0].second.size() == constants::idlen)
429 {
430 completions.insert(revision_id(decode_hexenc(sels[0].second)));
431 return;
432 }
433
434 complete_selector(project, sels, completions);
435}
436
437void
438diagnose_ambiguous_expansion(project_t & project,
439 string const & str,
440 set<revision_id> const & completions)
441{
442 if (completions.size() <= 1)
443 return;
444
445 string err = (F("selection '%s' has multiple ambiguous expansions:")
446 % str).str();
447 for (set<revision_id>::const_iterator i = completions.begin();
448 i != completions.end(); ++i)
449 err += ("\n" + describe_revision(project, *i));
450
451 N(false, i18n_format(err));
452}
453
454
455// Local Variables:
456// mode: C++
457// fill-column: 76
458// c-file-style: "gnu"
459// indent-tabs-mode: nil
460// End:
461// 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