Gentoo Archives: gentoo-commits

From: "Fabian Groffen (grobian)" <grobian@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] portage r10815 - in main/branches/prefix: bin cnf pym/_emerge pym/portage pym/portage/dbapi
Date: Fri, 27 Jun 2008 14:53:28
Message-Id: E1KCFKD-0000Yf-LM@stork.gentoo.org
1 Author: grobian
2 Date: 2008-06-27 14:53:20 +0000 (Fri, 27 Jun 2008)
3 New Revision: 10815
4
5 Modified:
6 main/branches/prefix/bin/portageq
7 main/branches/prefix/bin/repoman
8 main/branches/prefix/cnf/sets.conf
9 main/branches/prefix/pym/_emerge/__init__.py
10 main/branches/prefix/pym/portage/__init__.py
11 main/branches/prefix/pym/portage/dbapi/__init__.py
12 main/branches/prefix/pym/portage/dbapi/bintree.py
13 main/branches/prefix/pym/portage/output.py
14 main/branches/prefix/pym/portage/util.py
15 Log:
16 Merged from trunk 10801:10812
17
18 | 10802 | Handle KeyError from aux_get() inside |
19 | zmedico | dbapi._iter_match_slot() and _iter_match_use(). Thanks to |
20 | | grobian for reporting. |
21
22 | 10803 | Use settings["ROOT"] at the beginning of env_update() to |
23 | zmedico | avoid breakage due to "root" being an ObjectProxy instance. |
24
25 | 10804 | * Remove PORTAGE_LEGACY_GLOBALS hack for portage import |
26 | zmedico | since late initialization of portage.settings (via |
27 | | ObjectProxy) allows us to rely on being able to access the |
28 | | portage.exception namespace before portage.settings is |
29 | | initialized. * Use portage.settings["ROOT"] instead of |
30 | | portage.root to avoid potential ObjectProxy compatibility |
31 | | issues. |
32
33 | 10805 | Fix the PermissionDenied message for color.map. |
34 | zmedico | |
35
36 | 10806 | Add "PATH" to the set of allowed pacakge metadata keys for |
37 | zmedico | the PackageIndex. |
38
39 | 10807 | Call use_reduce() with matchall=1 when checking depstrings. |
40 | zmedico | Otherwise it's possible for invalid deps to be ignored. |
41 | | Thanks to grobian for reporting. |
42
43 | 10808 | For brevity, and consistency with pkgcore, rename |
44 | zmedico | "all-installed" to just "installed". Thanks to ferringb for |
45 | | the suggestion. |
46
47 | 10809 | Split the --skipfirst code into a resume_depgraph() function |
48 | zmedico | that will be useful for implementing a --keep-going option |
49 | | (bug #12768). |
50
51 | 10810 | Use bool() to simplify ObjectProxy.__nonzero__(). |
52 | zmedico | |
53
54 | 10811 | Bug #12768 - Add a --keep-going option. This uses the same |
55 | zmedico | resume_depgraph() function that's called for emerge --resume |
56 | | operations. Given the remaining tasks, depedencies are |
57 | | recalculated and any tasks with unsatisfied dependencies are |
58 | | automatically dropped. |
59
60 | 10812 | Call clear_caches() after the depgraph is out of scope, in |
61 | zmedico | MergeTask.merge(). |
62
63
64 Modified: main/branches/prefix/bin/portageq
65 ===================================================================
66 --- main/branches/prefix/bin/portageq 2008-06-27 14:47:19 UTC (rev 10814)
67 +++ main/branches/prefix/bin/portageq 2008-06-27 14:53:20 UTC (rev 10815)
68 @@ -462,25 +462,19 @@
69
70 global portage
71
72 - # First import the main portage module without legacy globals since it
73 - # is almost certain to succeed in that case. This provides access to
74 - # the portage.exception namespace which is needed for later exception
75 - # handling, like if portage.exception.PermissionDenied is raised when
76 - # constructing the legacy global config instance.
77 - os.environ["PORTAGE_LEGACY_GLOBALS"] = "false"
78 -
79 - # for an explanation on this logic, see pym/_emerge/__init__.py
80 - if os.environ.__contains__("PORTAGE_PYTHONPATH"):
81 - sys.path.insert(0, os.environ["PORTAGE_PYTHONPATH"])
82 - else:
83 - sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "pym"))
84 - import portage
85 -
86 - del os.environ["PORTAGE_LEGACY_GLOBALS"]
87 try:
88 - reload(portage)
89 + try:
90 + import portage
91 + except ImportError:
92 + from os import path as osp
93 + # for an explanation on this logic, see pym/_emerge/__init__.py
94 + if os.environ.__contains__("PORTAGE_PYTHONPATH"):
95 + sys.path.insert(0, os.environ["PORTAGE_PYTHONPATH"])
96 + else:
97 + sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "pym"))
98 + import portage
99 if uses_root:
100 - sys.argv[2] = portage.root
101 + sys.argv[2] = portage.settings["ROOT"]
102 retval = function(sys.argv[2:])
103 if retval:
104 sys.exit(retval)
105
106 Modified: main/branches/prefix/bin/repoman
107 ===================================================================
108 --- main/branches/prefix/bin/repoman 2008-06-27 14:47:19 UTC (rev 10814)
109 +++ main/branches/prefix/bin/repoman 2008-06-27 14:53:20 UTC (rev 10815)
110 @@ -1247,7 +1247,7 @@
111 mydeplist = []
112
113 try:
114 - portage.dep.use_reduce(mydeplist, excludeall=myiuse)
115 + portage.dep.use_reduce(mydeplist, matchall=1)
116 except portage.exception.InvalidDependString, e:
117 badsyntax.append(str(e))
118
119
120 Modified: main/branches/prefix/cnf/sets.conf
121 ===================================================================
122 --- main/branches/prefix/cnf/sets.conf 2008-06-27 14:47:19 UTC (rev 10814)
123 +++ main/branches/prefix/cnf/sets.conf 2008-06-27 14:53:20 UTC (rev 10815)
124 @@ -25,7 +25,7 @@
125 world-candidate = False
126
127 # Again, not much to change here, though people might prefer a different name
128 -[all-installed]
129 +[installed]
130 class = portage.sets.dbapi.EverythingSet
131 world-candidate = False
132
133
134 Modified: main/branches/prefix/pym/_emerge/__init__.py
135 ===================================================================
136 --- main/branches/prefix/pym/_emerge/__init__.py 2008-06-27 14:47:19 UTC (rev 10814)
137 +++ main/branches/prefix/pym/_emerge/__init__.py 2008-06-27 14:53:20 UTC (rev 10815)
138 @@ -212,6 +212,7 @@
139 "--fetchonly", "--fetch-all-uri",
140 "--getbinpkg", "--getbinpkgonly",
141 "--help", "--ignore-default-opts",
142 +"--keep-going",
143 "--noconfmem",
144 "--newuse", "--nocolor",
145 "--nodeps", "--noreplace",
146 @@ -5428,7 +5429,7 @@
147 world_set.update(all_added)
148 world_set.unlock()
149
150 - def loadResumeCommand(self, resume_data):
151 + def loadResumeCommand(self, resume_data, skip_masked=False):
152 """
153 Add a resume command to the graph and validate it in the process. This
154 will raise a PackageNotFound exception if a package is not available.
155 @@ -5441,7 +5442,6 @@
156 if not isinstance(mergelist, list):
157 mergelist = []
158
159 - skipfirst = "--skipfirst" in self.myopts
160 fakedb = self.mydbapi
161 trees = self.trees
162 serialized_tasks = []
163 @@ -5479,7 +5479,7 @@
164 root_config = self.roots[pkg.root]
165 if "merge" == pkg.operation and \
166 not visible(root_config.settings, pkg):
167 - if skipfirst:
168 + if skip_masked:
169 masked_tasks.append(Dependency(root=pkg.root, parent=pkg))
170 else:
171 self._unsatisfied_deps_for_display.append(
172 @@ -5533,7 +5533,7 @@
173 # was dropped via --skipfirst. It makes the
174 # resume list invalid, so convert it to a
175 # UnsatisfiedResumeDep exception.
176 - raise self.UnsatisfiedResumeDep(
177 + raise self.UnsatisfiedResumeDep(self,
178 masked_tasks + self._unsatisfied_deps)
179 self._serialized_tasks_cache = None
180 try:
181 @@ -5610,6 +5610,9 @@
182 can occur when a required package is dropped from the
183 merge list via --skipfirst.
184 """
185 + def __init__(self, depgraph, value):
186 + portage.exception.PortageException.__init__(self, value)
187 + self.depgraph = depgraph
188
189 class _internal_exception(portage.exception.PortageException):
190 def __init__(self, value=""):
191 @@ -5910,11 +5913,16 @@
192 "--fetchonly", "--fetch-all-uri",
193 "--nodeps", "--pretend"])
194
195 - def __init__(self, settings, trees, myopts):
196 + def __init__(self, settings, trees, mtimedb, myopts,
197 + spinner, mergelist, favorites):
198 self.settings = settings
199 self.target_root = settings["ROOT"]
200 self.trees = trees
201 self.myopts = myopts
202 + self._spinner = spinner
203 + self._mtimedb = mtimedb
204 + self._mergelist = mergelist
205 + self._favorites = favorites
206 self.edebug = 0
207 if settings.get("PORTAGE_DEBUG", "") == "1":
208 self.edebug = 1
209 @@ -5963,15 +5971,85 @@
210
211 return blocker_dblinks
212
213 - def merge(self, mylist, favorites, mtimedb):
214 - try:
215 - return self._merge(mylist, favorites, mtimedb)
216 - finally:
217 - if self._spawned_pids:
218 - from portage import process
219 - process.spawned_pids.extend(self._spawned_pids)
220 - self._spawned_pids = []
221 + def merge(self):
222
223 + keep_going = "--keep-going" in self.myopts
224 +
225 + while True:
226 + try:
227 + rval = self._merge()
228 + finally:
229 + spawned_pids = self._spawned_pids
230 + while spawned_pids:
231 + pid = spawned_pids.pop()
232 + try:
233 + if os.waitpid(pid, os.WNOHANG) == (0, 0):
234 + os.kill(pid, signal.SIGTERM)
235 + os.waitpid(pid, 0)
236 + except OSError:
237 + pass # cleaned up elsewhere.
238 +
239 + if rval == os.EX_OK or not keep_going:
240 + break
241 + mtimedb = self._mtimedb
242 + if "resume" not in mtimedb:
243 + break
244 + mergelist = self._mtimedb["resume"].get("mergelist")
245 + if not mergelist:
246 + break
247 + if mergelist[0][-1] != "merge":
248 + break
249 + # Skip the first one because it failed to build or install.
250 + del mergelist[0]
251 + if not mergelist:
252 + break
253 + mylist = self._calc_resume_list()
254 + clear_caches(self.trees)
255 + if not mylist:
256 + break
257 + self.curval += 1
258 + self._mergelist = mylist
259 +
260 + return rval
261 +
262 + def _calc_resume_list(self):
263 + """
264 + Use the current resume list to calculate a new one,
265 + dropping any packages with unsatisfied deps.
266 + """
267 + print colorize("GOOD", "*** Resuming merge...")
268 +
269 + show_spinner = "--quiet" not in self.myopts and \
270 + "--nodeps" not in self.myopts
271 +
272 + if show_spinner:
273 + print "Calculating dependencies ",
274 +
275 + myparams = create_depgraph_params(self.myopts, None)
276 + success, mydepgraph, dropped_tasks = resume_depgraph(
277 + self.settings, self.trees, self._mtimedb, self.myopts,
278 + myparams, self._spinner, skip_unsatisfied=True)
279 +
280 + if show_spinner:
281 + print "\b\b... done!"
282 +
283 + if not success:
284 + mydepgraph.display_problems()
285 + return None
286 +
287 + if dropped_tasks:
288 + portage.writemsg("!!! One or more packages have been " + \
289 + "dropped due to\n" + \
290 + "!!! masking or unsatisfied dependencies:\n\n",
291 + noiselevel=-1)
292 + for task in dropped_tasks:
293 + portage.writemsg(" " + str(task) + "\n", noiselevel=-1)
294 + portage.writemsg("\n", noiselevel=-1)
295 +
296 + mylist = mydepgraph.altlist()
297 + mydepgraph.break_refs(mylist)
298 + return mylist
299 +
300 def _poll_child_processes(self):
301 """
302 After each merge, collect status from child processes
303 @@ -5991,7 +6069,10 @@
304 pass
305 spawned_pids.remove(pid)
306
307 - def _merge(self, mylist, favorites, mtimedb):
308 + def _merge(self):
309 + mylist = self._mergelist
310 + favorites = self._favorites
311 + mtimedb = self._mtimedb
312 from portage.elog import elog_process
313 from portage.elog.filtering import filter_mergephases
314 buildpkgonly = "--buildpkgonly" in self.myopts
315 @@ -8483,6 +8564,73 @@
316 else:
317 print "Number removed: "+str(len(cleanlist))
318
319 +def resume_depgraph(settings, trees, mtimedb, myopts, myparams, spinner,
320 + skip_masked=False, skip_unsatisfied=False):
321 + """
322 + Construct a depgraph for the given resume list. This will raise
323 + PackageNotFound or depgraph.UnsatisfiedResumeDep when necessary.
324 + @rtype: tuple
325 + @returns: (success, depgraph, dropped_tasks)
326 + """
327 + mergelist = mtimedb["resume"]["mergelist"]
328 + dropped_tasks = set()
329 + while True:
330 + mydepgraph = depgraph(settings, trees,
331 + myopts, myparams, spinner)
332 + try:
333 + success = mydepgraph.loadResumeCommand(mtimedb["resume"],
334 + skip_masked=skip_masked)
335 + except depgraph.UnsatisfiedResumeDep, e:
336 + if not skip_unsatisfied:
337 + raise
338 +
339 + graph = mydepgraph.digraph
340 + unsatisfied_parents = dict((dep.parent, dep.parent) \
341 + for dep in e.value)
342 + traversed_nodes = set()
343 + unsatisfied_stack = list(unsatisfied_parents)
344 + while unsatisfied_stack:
345 + pkg = unsatisfied_stack.pop()
346 + if pkg in traversed_nodes:
347 + continue
348 + traversed_nodes.add(pkg)
349 +
350 + # If this package was pulled in by a parent
351 + # package scheduled for merge, removing this
352 + # package may cause the the parent package's
353 + # dependency to become unsatisfied.
354 + for parent_node in graph.parent_nodes(pkg):
355 + if not isinstance(parent_node, Package) \
356 + or parent_node.operation != "merge":
357 + continue
358 + unsatisfied = \
359 + graph.child_nodes(parent_node,
360 + ignore_priority=DepPriority.SOFT)
361 + if pkg in unsatisfied:
362 + unsatisfied_parents[parent_node] = parent_node
363 + unsatisfied_stack.append(parent_node)
364 +
365 + pruned_mergelist = [x for x in mergelist \
366 + if isinstance(x, list) and \
367 + tuple(x) not in unsatisfied_parents]
368 +
369 + # It shouldn't happen, but if the size of mergelist
370 + # does not decrease for some reason then the loop
371 + # will be infinite. Therefore, if that case ever
372 + # occurs for some reason, raise the exception to
373 + # break out of the loop.
374 + if not pruned_mergelist or \
375 + len(pruned_mergelist) == len(mergelist):
376 + raise
377 + mergelist[:] = pruned_mergelist
378 + dropped_tasks.update(unsatisfied_parents)
379 + del e, graph, traversed_nodes, \
380 + unsatisfied_parents, unsatisfied_stack
381 + continue
382 + else:
383 + break
384 + return (success, mydepgraph, dropped_tasks)
385 +
386 def action_build(settings, trees, mtimedb,
387 myopts, myaction, myfiles, spinner):
388
389 @@ -8606,66 +8754,18 @@
390 del mergelist[i]
391 break
392
393 - dropped_tasks = set()
394 -
395 + skip_masked = "--skipfirst" in myopts
396 + skip_unsatisfied = "--skipfirst" in myopts
397 success = False
398 + mydepgraph = None
399 try:
400 - while True:
401 - mydepgraph = depgraph(settings, trees,
402 - myopts, myparams, spinner)
403 - try:
404 - success = mydepgraph.loadResumeCommand(mtimedb["resume"])
405 - except depgraph.UnsatisfiedResumeDep, e:
406 - if "--skipfirst" not in myopts:
407 - raise
408 -
409 - graph = mydepgraph.digraph
410 - unsatisfied_parents = dict((dep.parent, dep.parent) \
411 - for dep in e.value)
412 - traversed_nodes = set()
413 - unsatisfied_stack = list(unsatisfied_parents)
414 - while unsatisfied_stack:
415 - pkg = unsatisfied_stack.pop()
416 - if pkg in traversed_nodes:
417 - continue
418 - traversed_nodes.add(pkg)
419 -
420 - # If this package was pulled in by a parent
421 - # package scheduled for merge, removing this
422 - # package may cause the the parent package's
423 - # dependency to become unsatisfied.
424 - for parent_node in graph.parent_nodes(pkg):
425 - if not isinstance(parent_node, Package) \
426 - or parent_node.operation != "merge":
427 - continue
428 - unsatisfied = \
429 - graph.child_nodes(parent_node,
430 - ignore_priority=DepPriority.SOFT)
431 - if pkg in unsatisfied:
432 - unsatisfied_parents[parent_node] = parent_node
433 - unsatisfied_stack.append(parent_node)
434 -
435 - pruned_mergelist = [x for x in mergelist \
436 - if isinstance(x, list) and \
437 - tuple(x) not in unsatisfied_parents]
438 -
439 - # It shouldn't happen, but if the size of mergelist
440 - # does not decrease for some reason then the loop
441 - # will be infinite. Therefore, if that case ever
442 - # occurs for some reason, raise the exception to
443 - # break out of the loop.
444 - if not pruned_mergelist or \
445 - len(pruned_mergelist) == len(mergelist):
446 - raise
447 - mergelist[:] = pruned_mergelist
448 - dropped_tasks.update(unsatisfied_parents)
449 - del e, graph, traversed_nodes, \
450 - unsatisfied_parents, unsatisfied_stack
451 - continue
452 - else:
453 - break
454 + success, mydepgraph, dropped_tasks = resume_depgraph(
455 + settings, trees, mtimedb, myopts, myparams, spinner,
456 + skip_masked=skip_masked, skip_unsatisfied=skip_unsatisfied)
457 except (portage.exception.PackageNotFound,
458 depgraph.UnsatisfiedResumeDep), e:
459 + if isinstance(e, depgraph.UnsatisfiedResumeDep):
460 + mydepgraph = e.depgraph
461 if show_spinner:
462 print
463 from textwrap import wrap
464 @@ -8734,7 +8834,8 @@
465 portage.writemsg("\n", noiselevel=-1)
466 del dropped_tasks
467 else:
468 - mydepgraph.display_problems()
469 + if mydepgraph is not None:
470 + mydepgraph.display_problems()
471 if not (ask or pretend):
472 # delete the current list and also the backup
473 # since it's probably stale too.
474 @@ -8873,7 +8974,6 @@
475
476 if ("--resume" in myopts):
477 favorites=mtimedb["resume"]["favorites"]
478 - mergetask = MergeTask(settings, trees, myopts)
479 if "PORTAGE_PARALLEL_FETCHONLY" in settings:
480 """ parallel-fetch uses --resume --fetchonly and we don't want
481 it to write the mtimedb"""
482 @@ -8884,7 +8984,9 @@
483 del mydepgraph
484 clear_caches(trees)
485
486 - retval = mergetask.merge(mymergelist, favorites, mtimedb)
487 + mergetask = MergeTask(settings, trees, mtimedb, myopts,
488 + spinner, mymergelist, favorites)
489 + retval = mergetask.merge()
490 merge_count = mergetask.curval
491 else:
492 if "resume" in mtimedb and \
493 @@ -8928,8 +9030,9 @@
494 del mydepgraph
495 clear_caches(trees)
496
497 - mergetask = MergeTask(settings, trees, myopts)
498 - retval = mergetask.merge(pkglist, favorites, mtimedb)
499 + mergetask = MergeTask(settings, trees, mtimedb, myopts,
500 + spinner, pkglist, favorites)
501 + retval = mergetask.merge()
502 merge_count = mergetask.curval
503
504 if retval == os.EX_OK and not (buildpkgonly or fetchonly or pretend):
505
506 Modified: main/branches/prefix/pym/portage/__init__.py
507 ===================================================================
508 --- main/branches/prefix/pym/portage/__init__.py 2008-06-27 14:47:19 UTC (rev 10814)
509 +++ main/branches/prefix/pym/portage/__init__.py 2008-06-27 14:53:20 UTC (rev 10815)
510 @@ -542,8 +542,8 @@
511 def env_update(makelinks=1, target_root=None, prev_mtimes=None, contents=None,
512 env=None):
513 if target_root is None:
514 - global root
515 - target_root = root
516 + global settings
517 + target_root = settings["ROOT"]
518 if prev_mtimes is None:
519 global mtimedb
520 prev_mtimes = mtimedb["ldpath"]
521
522 Modified: main/branches/prefix/pym/portage/dbapi/__init__.py
523 ===================================================================
524 --- main/branches/prefix/pym/portage/dbapi/__init__.py 2008-06-27 14:47:19 UTC (rev 10814)
525 +++ main/branches/prefix/pym/portage/dbapi/__init__.py 2008-06-27 14:53:20 UTC (rev 10815)
526 @@ -135,8 +135,11 @@
527
528 def _iter_match_slot(self, atom, cpv_iter):
529 for cpv in cpv_iter:
530 - if self.aux_get(cpv, ["SLOT"])[0] == atom.slot:
531 - yield cpv
532 + try:
533 + if self.aux_get(cpv, ["SLOT"])[0] == atom.slot:
534 + yield cpv
535 + except KeyError:
536 + continue
537
538 def _iter_match_use(self, atom, cpv_iter):
539 """
540 @@ -146,7 +149,10 @@
541 if self._iuse_implicit is None:
542 self._iuse_implicit = self.settings._get_implicit_iuse()
543 for cpv in cpv_iter:
544 - iuse, use = self.aux_get(cpv, ["IUSE", "USE"])
545 + try:
546 + iuse, use = self.aux_get(cpv, ["IUSE", "USE"])
547 + except KeyError:
548 + continue
549 use = use.split()
550 iuse = self._iuse_implicit.union(
551 re.escape(x.lstrip("+-")) for x in iuse.split())
552
553 Modified: main/branches/prefix/pym/portage/dbapi/bintree.py
554 ===================================================================
555 --- main/branches/prefix/pym/portage/dbapi/bintree.py 2008-06-27 14:47:19 UTC (rev 10814)
556 +++ main/branches/prefix/pym/portage/dbapi/bintree.py 2008-06-27 14:53:20 UTC (rev 10815)
557 @@ -173,6 +173,7 @@
558 "IUSE" : "",
559 "KEYWORDS": "",
560 "LICENSE" : "",
561 + "PATH" : "",
562 "PDEPEND" : "",
563 "PROVIDE" : "",
564 "RDEPEND" : "",
565
566 Modified: main/branches/prefix/pym/portage/output.py
567 ===================================================================
568 --- main/branches/prefix/pym/portage/output.py 2008-06-27 14:47:19 UTC (rev 10814)
569 +++ main/branches/prefix/pym/portage/output.py 2008-06-27 14:53:20 UTC (rev 10815)
570 @@ -231,8 +231,11 @@
571 parse_color_map(onerror=lambda e: writemsg("%s\n" % str(e), noiselevel=-1))
572 except FileNotFound:
573 pass
574 +except PermissionDenied, e:
575 + writemsg("Permission denied: '%s'\n" % str(e), noiselevel=-1)
576 + del e
577 except PortageException, e:
578 - writemsg("%s\n" % str(e))
579 + writemsg("%s\n" % str(e), noiselevel=-1)
580 del e
581
582 def nc_len(mystr):
583
584 Modified: main/branches/prefix/pym/portage/util.py
585 ===================================================================
586 --- main/branches/prefix/pym/portage/util.py 2008-06-27 14:47:19 UTC (rev 10814)
587 +++ main/branches/prefix/pym/portage/util.py 2008-06-27 14:53:20 UTC (rev 10815)
588 @@ -970,9 +970,7 @@
589 return object.__getattribute__(self, '_get_target')() != other
590
591 def __nonzero__(self):
592 - if object.__getattribute__(self, '_get_target')():
593 - return True
594 - return False
595 + return bool(object.__getattribute__(self, '_get_target')())
596
597 class LazyItemsDict(dict):
598 """A mapping object that behaves like a standard dict except that it allows
599
600 --
601 gentoo-commits@l.g.o mailing list