Merge lp:~jameinel/launchpad/lp-service into lp:launchpad

Proposed by John A Meinel
Status: Rejected
Rejected by: Martin Pool
Proposed branch: lp:~jameinel/launchpad/lp-service
Merge into: lp:launchpad
Diff against target: 10039 lines (+6605/-777)
133 files modified
Makefile (+1/-1)
bzrplugins/lpserve/__init__.py (+743/-4)
bzrplugins/lpserve/test_lpserve.py (+541/-0)
configs/development/launchpad-lazr.conf (+1/-0)
database/replication/Makefile (+15/-7)
database/replication/slon_ctl.py (+4/-2)
database/sampledata/current-dev.sql (+105/-70)
database/sampledata/current.sql (+269/-162)
database/schema/comments.sql (+45/-2)
database/schema/launchpad_session.sql (+3/-4)
database/schema/patch-2208-08-1.sql (+9/-0)
database/schema/patch-2208-08-2.sql (+8/-0)
database/schema/patch-2208-08-3.sql (+8/-0)
database/schema/patch-2208-09-0.sql (+38/-0)
database/schema/patch-2208-10-0.sql (+25/-0)
database/schema/patch-2208-11-0.sql (+30/-0)
database/schema/patch-2208-12-0.sql (+17/-0)
database/schema/patch-2208-13-0.sql (+17/-0)
database/schema/patch-2208-14-0.sql (+9/-0)
database/schema/patch-2208-15-0.sql (+7/-0)
database/schema/patch-2208-16-0.sql (+8/-0)
database/schema/patch-2208-17-0.sql (+18/-0)
database/schema/security.cfg (+57/-1)
lib/canonical/config/schema-lazr.conf (+17/-0)
lib/canonical/launchpad/icing/style-3-0.css.in (+1/-1)
lib/canonical/launchpad/interfaces/__init__.py (+2/-0)
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+10/-0)
lib/canonical/launchpad/scripts/runlaunchpad.py (+50/-0)
lib/canonical/launchpad/webapp/interfaces.py (+4/-0)
lib/canonical/launchpad/webapp/servers.py (+23/-19)
lib/canonical/launchpad/webapp/tests/test_servers.py (+14/-0)
lib/canonical/launchpad/zcml/librarian.zcml (+1/-1)
lib/lp/app/templates/base-layout-macros.pt (+2/-0)
lib/lp/bugs/browser/bugattachment.py (+11/-20)
lib/lp/bugs/browser/tests/test_bugattachment_edit_view.py (+107/-0)
lib/lp/bugs/configure.zcml (+46/-0)
lib/lp/bugs/doc/initial-bug-contacts.txt (+1/-1)
lib/lp/bugs/interfaces/bugtracker.py (+89/-0)
lib/lp/bugs/model/bugmessage.py (+2/-0)
lib/lp/bugs/model/bugsubscriptionfilter.py (+36/-0)
lib/lp/bugs/model/bugsubscriptionfilterimportance.py (+29/-0)
lib/lp/bugs/model/bugsubscriptionfilterstatus.py (+29/-0)
lib/lp/bugs/model/bugsubscriptionfiltertag.py (+29/-0)
lib/lp/bugs/model/bugtracker.py (+140/-7)
lib/lp/bugs/model/tests/test_bugsubscriptionfilter.py (+66/-0)
lib/lp/bugs/model/tests/test_bugsubscriptionfilterimportance.py (+54/-0)
lib/lp/bugs/model/tests/test_bugsubscriptionfilterstatus.py (+52/-0)
lib/lp/bugs/model/tests/test_bugsubscriptionfiltertag.py (+51/-0)
lib/lp/bugs/tests/has-bug-supervisor.txt (+1/-1)
lib/lp/bugs/tests/test_bugtracker_components.py (+176/-0)
lib/lp/buildmaster/interfaces/buildfarmjob.py (+10/-1)
lib/lp/buildmaster/interfaces/packagebuild.py (+0/-6)
lib/lp/buildmaster/model/buildfarmjob.py (+5/-2)
lib/lp/code/browser/branch.py (+14/-0)
lib/lp/code/browser/branchmergeproposal.py (+9/-2)
lib/lp/code/browser/codereviewcomment.py (+39/-35)
lib/lp/code/browser/configure.zcml (+10/-3)
lib/lp/code/browser/tests/test_branchmergeproposal.py (+21/-2)
lib/lp/code/browser/tests/test_codereviewcomment.py (+26/-9)
lib/lp/code/doc/branch.txt (+1/-0)
lib/lp/code/interfaces/branchrevision.py (+0/-2)
lib/lp/code/model/branch.py (+11/-3)
lib/lp/code/model/branchmergeproposal.py (+1/-1)
lib/lp/code/model/branchrevision.py (+1/-2)
lib/lp/code/model/tests/test_branchjob.py (+1/-3)
lib/lp/code/templates/codereviewcomment-body.pt (+2/-2)
lib/lp/code/templates/codereviewcomment-header.pt (+3/-3)
lib/lp/codehosting/sshserver/session.py (+289/-11)
lib/lp/codehosting/sshserver/tests/test_session.py (+73/-0)
lib/lp/codehosting/tests/test_lpserve.py (+11/-60)
lib/lp/registry/browser/configure.zcml (+25/-0)
lib/lp/registry/browser/distroseries.py (+9/-8)
lib/lp/registry/browser/distroseriesdifference.py (+160/-0)
lib/lp/registry/browser/tests/test_distroseriesdifference_views.py (+293/-0)
lib/lp/registry/browser/tests/test_distroseriesdifference_webservice.py (+99/-0)
lib/lp/registry/browser/tests/test_series_views.py (+35/-7)
lib/lp/registry/browser/tests/test_sourcepackage_views.py (+1/-1)
lib/lp/registry/configure.zcml (+30/-97)
lib/lp/registry/enum.py (+4/-4)
lib/lp/registry/interfaces/distroseriesdifference.py (+54/-5)
lib/lp/registry/interfaces/distroseriesdifferencecomment.py (+19/-2)
lib/lp/registry/interfaces/productseries.py (+1/-10)
lib/lp/registry/interfaces/structuralsubscription.py (+48/-30)
lib/lp/registry/javascript/distroseriesdifferences_details.js (+304/-0)
lib/lp/registry/model/distroseriesdifference.py (+102/-27)
lib/lp/registry/model/distroseriesdifferencecomment.py (+28/-3)
lib/lp/registry/model/milestone.py (+5/-3)
lib/lp/registry/model/structuralsubscription.py (+20/-31)
lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt (+1/-1)
lib/lp/registry/templates/distroseries-localdifferences.pt (+16/-7)
lib/lp/registry/templates/distroseriesdifference-listing-extra.pt (+62/-0)
lib/lp/registry/templates/distroseriesdifferencecomment-fragment.pt (+2/-0)
lib/lp/registry/tests/test_distroseriesdifference.py (+168/-20)
lib/lp/registry/tests/test_distroseriesdifferencecomment.py (+26/-2)
lib/lp/registry/windmill/tests/test_distroseriesdifference_expander.py (+86/-0)
lib/lp/services/comments/browser/configure.zcml (+12/-4)
lib/lp/services/comments/interfaces/conversation.py (+18/-0)
lib/lp/services/comments/templates/comment-body.pt (+8/-0)
lib/lp/services/comments/templates/comment-header.pt (+9/-0)
lib/lp/soyuz/browser/configure.zcml (+0/-7)
lib/lp/soyuz/browser/distroarchseries.py (+2/-1)
lib/lp/soyuz/browser/tests/test_distroarchseries_view.py (+51/-0)
lib/lp/soyuz/configure.zcml (+12/-0)
lib/lp/soyuz/doc/distroarchseries.txt (+10/-0)
lib/lp/soyuz/doc/package-arch-specific.txt (+11/-0)
lib/lp/soyuz/interfaces/distributionjob.py (+66/-0)
lib/lp/soyuz/interfaces/distroarchseries.py (+6/-0)
lib/lp/soyuz/model/distributionjob.py (+112/-0)
lib/lp/soyuz/model/distroarchseries.py (+1/-0)
lib/lp/soyuz/model/initialisedistroseriesjob.py (+48/-0)
lib/lp/soyuz/model/publishing.py (+7/-1)
lib/lp/soyuz/model/queue.py (+18/-8)
lib/lp/soyuz/pas.py (+2/-1)
lib/lp/soyuz/scripts/initialise_distroseries.py (+2/-1)
lib/lp/soyuz/scripts/tests/test_copypackage.py (+38/-0)
lib/lp/soyuz/scripts/tests/test_initialise_distroseries.py (+21/-0)
lib/lp/soyuz/tests/test_initialisedistroseriesjob.py (+74/-0)
lib/lp/soyuz/tests/test_publishing.py (+22/-20)
lib/lp/soyuz/tests/test_publishing_top_level_api.py (+40/-1)
lib/lp/testing/__init__.py (+40/-4)
lib/lp/testing/factory.py (+58/-1)
lib/lp/testing/tests/test_testing.py (+43/-0)
lib/lp/translations/browser/configure.zcml (+17/-0)
lib/lp/translations/browser/tests/test_translationtemplatesbuild.py (+84/-0)
lib/lp/translations/browser/translationtemplatesbuild.py (+63/-0)
lib/lp/translations/configure.zcml (+17/-0)
lib/lp/translations/interfaces/translationtemplatesbuild.py (+49/-0)
lib/lp/translations/model/translationtemplatesbuild.py (+117/-0)
lib/lp/translations/model/translationtemplatesbuildbehavior.py (+4/-2)
lib/lp/translations/model/translationtemplatesbuildjob.py (+21/-14)
lib/lp/translations/stories/buildfarm/xx-build-summary.txt (+89/-4)
lib/lp/translations/templates/translationtemplatesbuild-index.pt (+133/-0)
lib/lp/translations/tests/test_translationtemplatesbuild.py (+134/-0)
To merge this branch: bzr merge lp:~jameinel/launchpad/lp-service
Reviewer Review Type Date Requested Status
John A Meinel (community) Needs Resubmitting
Andrew Bennetts (community) Needs Resubmitting
Jonathan Lange Pending
Review via email: mp+35877@code.launchpad.net

Commit message

Implement LaunchpadForkingService to speed up bzr+ssh connection times.

Description of the change

I'm pretty sure this code is based on the 'launchpad/devel' branch, but it might be db-devel.

The goal of this submission is to improve the time for "bzr+ssh" to connect and be useful. The new method must be activated by setting:
  [codehosting]
  use_forking_server = True

It is set to be enabled in "development" mode, but the default is still disabled. I can't give a recommendation for the production config, because the branch is private.

This implements a new service (LaunchpadForkingService). It sits on a socket and waits for a request to 'fork <command>'. When received, it creates a stdin/stdout/stderr fifo on disk, and forks itself, running 'run_bzr_*(command)', rather than using 'exec()' which requires bootstrapping the python process.

The benefit is seen with: time echo hello | ssh localhost bzr serve --inet ...

Without this patch, it is 2.5s to serve on the loopback. With this patch, it is 0.25s.

I'm very happy to work with someone to smooth out the finer points of this submission. I tried to be explicit about places in the code that I had a decision to make, and why I chose the method I did.

I didn't use the FeatureFlag system, because setting it up via the config file was a lot more straightforward. (globally enabling/disabling the functionality).
As near as I can tell, there should be no impact of rolling this code out on production, if use_forking_daemon: False.

(The make run_codehosting will not actually spawn the daemon, and the twisted Conch code just gets an 'if ' check to see that it should use the old code path.)

I haven't run the full test suite, but I have:
 1) Run all of the locally relevant tests "bzr selftest -s bt.lp_serve' and 'bin/test
    lp.codehosting.sshserver
 2) Manually started the sftp service and run commands through the local instance. Both with
    and without the forking service enabled. (And I have confirmed that it isn't used when
    disabled, and is used when enabled, etc.)

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

small update, I removed the "connect_to_lpservice.py" script. It was old prototyping code, which isn't relevant anymore.

Revision history for this message
Martin Pool (mbp) wrote :

That's a great improvement in time.

I think (per your decision 1) it would be good to change it to a unix-domain socket, for the sake of a bit more security, and better reusability as a per-user thing.

Per http://www.pathname.com/fhs/pub/fhs-2.3.html#VARRUNRUNTIMEVARIABLEDATA

> System programs that maintain transient UNIX-domain sockets must place them in this directory.

which will require sysadmins making a directory under there that's writable by this process. That can be in the config file. You should just need socket.bind((AF_UNIX, path)). Then just chmod the socket.

Having a config option to turn this on is great. We could make it active only on staging at first and see the effect.

481 + while not self._should_terminate.isSet():
482 + try:
483 + conn, client_addr = self._server_socket.accept()
484 + except self._socket_timeout:
485 + pass # run shutdown and children checks
486 + except self._socket_error, e:
487 + if e.args[0] == errno.EINTR:
488 + pass # run shutdown and children checks
489 + elif e.args[0] != errno.EBADF:
490 + # We can get EBADF here while we are shutting down
491 + # So we just ignore it for now
492 + pass

It seems like this might spin if the server socket's closed and there are no dead children.

+ fifos on the filesystem, and start running the requseted command. The

typo

We should do some kind of plan or review with LOSAs on how to deploy and support this. We should configure it to log to a file, and for that to be synced to devpad. Putting the pid in the log file would be nice.

Revision history for this message
John A Meinel (jameinel) wrote :

First off, my apologies for accidentally including some db changes in this patch.

I went for a default socket location of '/var/run/launchpad_forking_service.sock' and an lp configured '/var/tmp/launchpad_forking_service.sock', since that matched a lot of the other stuff in schema-lazr.conf. I assume we can set it wherever we want in the end.

...

> It seems like this might spin if the server socket's closed and there are no dead children.

I'm not sure how you would get the server socket closed inside this process. Regardless, children shouldn't affect whether it spins or not. We just need a way to set '_should_terminate', which can be done via SIGINT or SIGTERM.

So, yes, if self._server_socket.accept() always raises an error but the should terminate event is not set, we will spin.

I suppose I could put a "self._should_terminate.set()" inside the "EBADF" check. Would that be better for you?

I do put PID into the log file, but bzr does not for each mutter() request. So when spawning I indicate that PID started, etc. But each individual message doesn't. mutter() does its own formatting, so it is a matter of changing these lines:
=== modified file 'bzrlib/trace.py'
--- bzrlib/trace.py 2010-06-28 02:41:22 +0000
+++ bzrlib/trace.py 2010-09-22 20:15:56 +0000
@@ -183,7 +183,7 @@
     else:
         out = fmt
     now = time.time()
- timestamp = '%0.3f ' % (now - _bzr_log_start_time,)
+ timestamp = '%0.3f [%d] ' % (now - _bzr_log_start_time, os.getpid())
     out = timestamp + out + '\n'
     _trace_file.write(out)
     # there's no explicit flushing; the file is typically line buffered.

Revision history for this message
Andrew Bennetts (spiv) wrote :
Download full text (9.8 KiB)

There are many unrelated changes in this diff, which is part of why the line
count is so enormous. I guess the target branch isn't the one you branched
from? It does make the review harder than it should be, and certainly much
more intimidating at a glance than it deserves, so it'd be worth fixing :)

SIGCHLD: is this handler uninstalled in the child? If not, there's a risk of
EINTR issues if the child spawns its own subprocesses. Ah, yes, it is. Good :)

> + # [Decision #4]
> + # Exit information
> + # How do we inform the client process that the child has exited?

This section seems to describe the questions without clearly stating what the
answer was. I have to read through the wall of text fairly carefully to see
what the choice was.

> + # There is some possibility that files won't get flushed, etc. So we
> + # may want to be calling sys.exitfunc() first. Note that bzr itself

Yes, I think it would be good to call sys.exitfunc() for the reasons you give.
I'd be particularly worried about incompletely written log files... it would be
a shame to have the most interesting log content never get flushed! (The end of
the log is often the most interesting bit.)

> + # [Decision #8]
> + # env vars

This section doesn't make clear what the issue is, nor what the conclusion is.
What does “this data” refer to? Are env vars the problem, or solution to some
problem?

> + # [Decision #9]
> + # nicer errors to clients
> + # This service is meant to be run only on the local system. As such,
> + # we don't try to be extra defensive about leaking information to
> + # clients. Instead we try to be helpful, and tell them as much as we
> + # know about what went wrong.

The client is connected to a remote user though. So the information returned
here won't be repeated back to that remote client?

I'm worried about this becase we already echo more information back to the
remote client than really makes sense, e.g. random warnings on stderr are sent
to the remote client, and tend to confuse users.

> + _fork_function = os.fork

I assume this is to make unit testing easier, which is fine. Peeking ahead in
the diff I see that you test this at least in part by subclassing, which isn't
so great: I feel it violates the “Use the front door first” guideline for tests,
because this isn't a class intended for subclassing in production code, so the
tests are assuming a more intimate (and not sharply defined) interface with this
code than is ideal. I'm not sure it's worth spending effort trying to find a
better way, but hopefully you finding this interesting food for thought.

> + # XXX: Cheap hack. by this point bzrlib has opened stderr for logging
> + # (as part of starting the service process in the first place). As
> + # such, it has a stream handler that writes to stderr. logging
> + # tries to flush and close that, but the file is already closed.
> + # This just supresses that exception
> + logging.raiseExceptions = False

IIRC the Launchpad coding standard requires XXX comments to have a name and date
a...

review: Needs Resubmitting
Revision history for this message
Martin Pool (mbp) wrote :

>> === added file 'lp_service_interface.txt'
> I don't think this belongs in the root of the tree.

fwiw, my inclination is to add docstrings, not comments or text files, because they more easily show up in <http://people.canonical.com/~mwh/canonicalapi/>. I don't know what other lp devs would say.

Revision history for this message
Martin Pool (mbp) wrote :

<mwhudson> poolie: the branch has conflicts with db-devel it seems
<mwhudson> jam: can you merge db-devel, fix the conflicts and repropose it against db-devel?

Revision history for this message
John A Meinel (jameinel) wrote :

I've done the changes, but I'm rejecting this in favor of an updated merge w/ db-devel and proposing against db-devel directly.

review: Needs Resubmitting
Revision history for this message
John A Meinel (jameinel) wrote :

(note that I'm not authorized to reject my own submission, I can only delete it, but I don't want to lose the conversation)

Revision history for this message
Andrew Bennetts (spiv) wrote :
Download full text (18.0 KiB)

John Arbash Meinel wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> Unfortunately this won't get put on the MP, because Launchpad now
> rejects all my emails. But I really wanted to reply to your comments inline.

Ok. I won't trim the quoted text for other people's benefit. Warm up those
scrolling fingers!

>
>
>
> ...
>
> >> + # [Decision #4]
> >> + # Exit information
> >> + # How do we inform the client process that the child has exited?
> >
> > This section seems to describe the questions without clearly stating what the
> > answer was. I have to read through the wall of text fairly carefully to see
> > what the choice was.
>
> New summary.
>
> >
> >> + # There is some possibility that files won't get flushed, etc. So we
> >> + # may want to be calling sys.exitfunc() first. Note that bzr itself
> >
> > Yes, I think it would be good to call sys.exitfunc() for the reasons you give.
> > I'd be particularly worried about incompletely written log files... it would be
> > a shame to have the most interesting log content never get flushed! (The end of
> > the log is often the most interesting bit.)
>
> I switched to this, though I'm not really sure it is the right thing.
> (When *do* buffered files get synced to disk? It seems that it might be
> during stack unwinding, which we explicitly avoid via os._exitfunc(),
> which bzr has been doing for a long time now...)

We call os._exitfunc from the outermost frame, so we don't avoid any stack
unwinding.

We do it to avoid wasting time that would be spent garbage collecting perhaps
100000s of objects when the OS can free that memory for us more cheaply.

>
>
> >
> >> + # [Decision #8]
> >> + # env vars
> >
> > This section doesn't make clear what the issue is, nor what the conclusion is.
> > What does “this data” refer to? Are env vars the problem, or solution to some
> > problem?
>
> Removed. Passing env vars from the Twisted process to the spawned child
> is a bit tricky. I did end up going with a syntax that can be parsed
> without ambiguity, though it is limited to 8-bit without encoding
> information. (AFAIK, so are real env vars)
>
> >
> >> + # [Decision #9]
> >> + # nicer errors to clients
> >> + # This service is meant to be run only on the local system. As such,
> >> + # we don't try to be extra defensive about leaking information to
> >> + # clients. Instead we try to be helpful, and tell them as much as we
> >> + # know about what went wrong.
> >
> > The client is connected to a remote user though. So the information returned
> > here won't be repeated back to that remote client?
>
> Wrong client. The 'client' here is twisted. I clarified it with:
> nicer errors on the request socket

Ok.

>
> >
> > I'm worried about this becase we already echo more information back to the
> > remote client than really makes sense, e.g. random warnings on stderr are sent
> > to the remote client, and tend to confuse users.
>
> That is true, though often the alternative would be to tell them
> nothing, which would have been worse. (Getting Permission Error can be
> confusing when i...

Revision history for this message
Martin Pool (mbp) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2010-09-07 18:15:01 +0000
3+++ Makefile 2010-10-04 22:08:24 +0000
4@@ -253,7 +253,7 @@
5
6 run_codehosting: check_schema inplace stop
7 $(RM) thread*.request
8- bin/run -r librarian,sftp,codebrowse -i $(LPCONFIG)
9+ bin/run -r librarian,sftp,forker,codebrowse -i $(LPCONFIG)
10
11
12 start_librarian: compile
13
14=== added directory 'bzrplugins/lpserve'
15=== renamed file 'bzrplugins/lpserve.py' => 'bzrplugins/lpserve/__init__.py'
16--- bzrplugins/lpserve.py 2010-04-19 06:35:23 +0000
17+++ bzrplugins/lpserve/__init__.py 2010-10-04 22:08:24 +0000
18@@ -8,15 +8,33 @@
19
20 __metaclass__ = type
21
22-__all__ = ['cmd_launchpad_server']
23-
24-
25+__all__ = ['cmd_launchpad_server',
26+ 'cmd_launchpad_forking_service',
27+ ]
28+
29+
30+import errno
31+import os
32 import resource
33+import shlex
34+import shutil
35+import signal
36+import socket
37 import sys
38+import tempfile
39+import threading
40+import time
41
42 from bzrlib.commands import Command, register_command
43 from bzrlib.option import Option
44-from bzrlib import lockdir, ui
45+from bzrlib import (
46+ commands,
47+ errors,
48+ lockdir,
49+ osutils,
50+ trace,
51+ ui,
52+ )
53
54 from bzrlib.smart import medium, server
55 from bzrlib.transport import get_transport
56@@ -110,3 +128,724 @@
57
58
59 register_command(cmd_launchpad_server)
60+
61+
62+class LPForkingService(object):
63+ """A service that can be asked to start a new bzr subprocess via fork.
64+
65+ The basic idea is that python startup is very expensive. For example, the
66+ original 'lp-serve' command could take 2.5s just to start up, before any
67+ actual actions could be performed.
68+
69+ This class provides a service sitting on a socket, which can then be
70+ requested to fork and run a given bzr command.
71+
72+ Clients connect to the socket and make a simple request, which then
73+ receives a response. The possible requests are:
74+
75+ "hello\n": Trigger a heartbeat to report that the program is still
76+ running, and write status information to the log file.
77+ "quit\n": Stop the service, but do so 'nicely', waiting for children
78+ to exit, etc. Once this is received the service will stop
79+ taking new requests on the port.
80+ "fork <command>\n": Request a new subprocess to be started.
81+ <command> is the bzr command to be run, such as "rocks" or
82+ "lp-serve --inet 12".
83+ The immediate response will be the path-on-disk to a directory full
84+ of named pipes (fifos) that will be the stdout/stderr/stdin of the
85+ new process.
86+ If a client holds the socket open, when the child process exits,
87+ the exit status (as given by 'wait()') will be written to the
88+ socket.
89+
90+ Note that one of the key bits is that the client will not be
91+ started with exec*, we just call 'commands.run_bzr*()' directly.
92+ This way, any modules that are already loaded will not need to be
93+ loaded again. However, care must be taken with any global-state
94+ that should be reset.
95+ """
96+
97+ # Design decisions. These are bits where we could have chosen a different
98+ # method/implementation and weren't sure what would be best. Documenting
99+ # the current decision, and the alternatives.
100+ #
101+ # [Decision #1]
102+ # Serve on a named AF_UNIX socket.
103+ # 1) It doesn't make sense to serve to arbitrary hosts, we only want
104+ # the local host to make requests. (Since the client needs to
105+ # access the named fifos on the current filesystem.)
106+ # 2) You can set security parameters on a filesystem path (g+rw,
107+ # a-rw).
108+ # [Decision #2]
109+ # SIGCHLD
110+ # We want to quickly detect that children have exited so that we can
111+ # inform the client process quickly. At the moment, we register a
112+ # SIGCHLD handler that doesn't do anything. However, it means that
113+ # when we get the signal, if we are currently blocked in something
114+ # like '.accept()', we will jump out temporarily. At that point the
115+ # main loop will check if any children have exited. We could have
116+ # done this work as part of the signal handler, but that felt 'racy'
117+ # doing any serious work in a signal handler.
118+ # If we just used socket.timeout as the indicator to go poll for
119+ # children exiting, it slows the disconnect by as much as the full
120+ # timeout. (So a timeout of 1.0s will cause the process to hang by
121+ # that long until it determines that a child has exited, and can
122+ # close the connection.)
123+ # The current flow means that we'll notice exited children whenever
124+ # we finish the current work.
125+ # [Decision #3]
126+ # Child vs Parent actions.
127+ # There are several actions that are done when we get a new request.
128+ # We have to create the fifos on disk, fork a new child, connect the
129+ # child to those handles, and inform the client of the new path (not
130+ # necessarily in that order.) It makes sense to wait to send the path
131+ # message until after the fifos have been created. That way the
132+ # client can just try to open them immediately, and the
133+ # client-and-child will be synchronized by the open() calls.
134+ # However, should the client be the one doing the mkfifo, should the
135+ # server? Who should be sending the message? Should we fork after the
136+ # mkfifo or before.
137+ # The current thoughts:
138+ # 1) Try to do work in the child when possible. This should allow
139+ # for 'scaling' because the server is single-threaded.
140+ # 2) We create the directory itself in the server, because that
141+ # allows the server to monitor whether the client failed to
142+ # clean up after itself or not.
143+ # 3) Otherwise we create the fifos in the client, and then send
144+ # the message back.
145+ # [Decision #4]
146+ # Exit information
147+ # Inform the client that the child has exited on the socket they used
148+ # to request the fork.
149+ # 1) Arguably they could see that stdout and stderr have been closed,
150+ # and thus stop reading. In testing, I wrote a client which uses
151+ # select.poll() over stdin/stdout/stderr and used that to ferry
152+ # the content to the appropriate local handle. However for the
153+ # FIFOs, when the remote end closed, I wouldn't see any
154+ # corresponding information on the local end. There obviously
155+ # wasn't any data to be read, so they wouldn't show up as
156+ # 'readable' (for me to try to read, and get 0 bytes, indicating
157+ # it was closed). I also wasn't seeing POLLHUP, which seemed to be
158+ # the correct indicator. As such, we decided to inform the client
159+ # on the socket that they originally made the fork request, rather
160+ # than just closing the socket immediately.
161+ # 2) We could have had the forking server close the socket, and only
162+ # the child hold the socket open. When the child exits, then the
163+ # OS naturally closes the socket.
164+ # If we want the returncode, then we should put that as bytes on
165+ # the socket before we exit. Having the child do the work means
166+ # that in error conditions, it could easily die before being able to
167+ # write anything (think SEGFAULT, etc). The forking server is
168+ # already 'wait'() ing on its children. So that we don't get
169+ # zombies, and with wait3() we can get the rusage (user time,
170+ # memory consumption, etc.)
171+ # As such, it seems reasonable that the server can then also
172+ # report back when a child is seen as exiting.
173+ # [Decision #5]
174+ # cleanup once connected
175+ # The child process blocks during 'open()' waiting for the client to
176+ # connect to its fifos. Once the client has connected, the child then
177+ # deletes the temporary directory and the fifos from disk. This means
178+ # that there isn't much left for diagnosis, but it also means that
179+ # the client won't leave garbage around if it crashes, etc.
180+ # Note that the forking service itself still monitors the paths
181+ # created, and will delete garbage if it sees that a child failed to
182+ # do so.
183+ # [Decision #6]
184+ # os._exit(retcode) in the child
185+ # Calling sys.exit(retcode) raises an exception, which then bubbles
186+ # up the stack and runs exit functions (and finally statements). When
187+ # I tried using it originally, I would see the current child bubble
188+ # all the way up the stack (through the server code that it fork()
189+ # through), and then get to main() returning code 0. The process
190+ # would still exit nonzero. My guess is that something in the atexit
191+ # functions was failing, but that it was happening after logging, etc
192+ # had been shut down.
193+ # Any global state from the child process should be flushed before
194+ # run_bzr_* has exited (which we *do* wait for), and any other global
195+ # state is probably a remnant from the service process. Which will be
196+ # cleaned up by the service itself, rather than the child.
197+ # There is some concern that log files may not get flushed, so we
198+ # currently call sys.exitfunc() first. The main problem is that I
199+ # don't know any way to *remove* a function registered via 'atexit()'
200+ # so if the forking service has some state, we my try to clean it up
201+ # incorrectly.
202+ # Note that the bzr script itself uses sys.exitfunc(); os._exit() in
203+ # the 'bzr' main script, as the teardown time of all the python state
204+ # was quite noticeable in real-world runtime. As such, bzrlib should
205+ # be pretty safe, or it would have been failing for people already.
206+ # [Decision #7]
207+ # prefork vs max children vs ?
208+ # For simplicity it seemed easiest to just fork when requested. Over
209+ # time, I realized it would be easy to allow running an arbitrary
210+ # command (no harder than just running one command), so it seemed
211+ # reasonable to switch over. If we go the prefork route, then we'll
212+ # need a way to tell the pre-forked children what command to run.
213+ # This could be as easy as just adding one more fifo that they wait
214+ # on in the same directory.
215+ # For now, I've chosen not to limit the number of forked children. I
216+ # don't know what a reasonable value is, and probably there are
217+ # already limitations at play. (If Conch limits connections, then it
218+ # will already be doing all the work, etc.)
219+ # [Decision #8]
220+ # nicer errors on the request socket
221+ # This service is meant to be run only on the local system. As such,
222+ # we don't try to be extra defensive about leaking information to
223+ # the one connecting to the socket. (We should still watch out what
224+ # we send across the per-child fifos, since those are connected to
225+ # remote clients.) Instead we try to be helpful, and tell them as
226+ # much as we know about what went wrong.
227+
228+ DEFAULT_PATH = '/var/run/launchpad_forking_service.sock'
229+ DEFAULT_PERMISSIONS = 00660 # Permissions on the master socket (rw-rw----)
230+ WAIT_FOR_CHILDREN_TIMEOUT = 5*60 # Wait no more than 5 min for children
231+ SOCKET_TIMEOUT = 1.0
232+ SLEEP_FOR_CHILDREN_TIMEOUT = 1.0
233+ WAIT_FOR_REQUEST_TIMEOUT = 1.0 # No request should take longer than this to
234+ # be read
235+
236+ _fork_function = os.fork
237+
238+ def __init__(self, path=DEFAULT_PATH, perms=DEFAULT_PERMISSIONS):
239+ self.master_socket_path = path
240+ self._perms = perms
241+ self._start_time = time.time()
242+ self._should_terminate = threading.Event()
243+ # We address these locally, in case of shutdown socket may be gc'd
244+ # before we are
245+ self._socket_timeout = socket.timeout
246+ self._socket_error = socket.error
247+ self._socket_timeout = socket.timeout
248+ self._socket_error = socket.error
249+ # Map from pid => information
250+ self._child_processes = {}
251+ self._children_spawned = 0
252+
253+ def _create_master_socket(self):
254+ self._server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
255+ self._server_socket.bind(self.master_socket_path)
256+ if self._perms is not None:
257+ os.chmod(self.master_socket_path, self._perms)
258+ self._server_socket.setsockopt(socket.SOL_SOCKET,
259+ socket.SO_REUSEADDR, 1)
260+ self._sockname = self._server_socket.getsockname()
261+ # self.host = self._sockname[0]
262+ self.port = self._sockname[1]
263+ self._server_socket.listen(5)
264+ self._server_socket.settimeout(self.SOCKET_TIMEOUT)
265+ trace.mutter('set socket timeout to: %s' % (self.SOCKET_TIMEOUT,))
266+
267+ def _cleanup_master_socket(self):
268+ self._server_socket.close()
269+ try:
270+ os.remove(self.master_socket_path)
271+ except (OSError, IOError), e:
272+ # If we don't delete it, then we get 'address already in
273+ # use' failures
274+ trace.mutter('failed to cleanup: %s'
275+ % (self.master_socket_path,))
276+
277+ def _handle_sigchld(self, signum, frm):
278+ # We don't actually do anything here, we just want an interrupt (EINTR)
279+ # on socket.accept() when SIGCHLD occurs.
280+ pass
281+
282+ def _handle_sigterm(self, signum, frm):
283+ # Unregister this as the default handler, 2 SIGTERMs will exit us.
284+ signal.signal(signal.SIGTERM, signal.SIG_DFL)
285+ # SIGTERM should also generate EINTR on our wait loop, so this should
286+ # be enough
287+ self._should_terminate.set()
288+
289+ def _register_signals(self):
290+ """Register a SIGCHILD and SIGTERM handler.
291+
292+ If we have a trigger for SIGCHILD then we can quickly respond to
293+ clients when their process exits. The main risk is getting more EAGAIN
294+ errors elsewhere.
295+
296+ SIGTERM allows us to cleanup nicely before we exit.
297+ """
298+ signal.signal(signal.SIGCHLD, self._handle_sigchld)
299+ signal.signal(signal.SIGTERM, self._handle_sigterm)
300+
301+ def _unregister_signals(self):
302+ signal.signal(signal.SIGCHLD, signal.SIG_DFL)
303+ signal.signal(signal.SIGTERM, signal.SIG_DFL)
304+
305+ def _create_child_file_descriptors(self, base_path):
306+ stdin_path = os.path.join(base_path, 'stdin')
307+ stdout_path = os.path.join(base_path, 'stdout')
308+ stderr_path = os.path.join(base_path, 'stderr')
309+ os.mkfifo(stdin_path)
310+ os.mkfifo(stdout_path)
311+ os.mkfifo(stderr_path)
312+
313+ def _bind_child_file_descriptors(self, base_path):
314+ import logging
315+ from bzrlib import ui
316+ stdin_path = os.path.join(base_path, 'stdin')
317+ stdout_path = os.path.join(base_path, 'stdout')
318+ stderr_path = os.path.join(base_path, 'stderr')
319+ # Opening for writing blocks (or fails), so do those last
320+ # TODO: Consider buffering, though that might interfere with reading
321+ # and writing the smart protocol
322+ stdin_fid = os.open(stdin_path, os.O_RDONLY)
323+ stdout_fid = os.open(stdout_path, os.O_WRONLY)
324+ stderr_fid = os.open(stderr_path, os.O_WRONLY)
325+ # Note: by this point bzrlib has opened stderr for logging
326+ # (as part of starting the service process in the first place). As
327+ # such, it has a stream handler that writes to stderr. logging
328+ # tries to flush and close that, but the file is already closed.
329+ # This just supresses that exception
330+ logging.raiseExceptions = False
331+ sys.stdin.close()
332+ sys.stdout.close()
333+ sys.stderr.close()
334+ os.dup2(stdin_fid, 0)
335+ os.dup2(stdout_fid, 1)
336+ os.dup2(stderr_fid, 2)
337+ sys.stdin = os.fdopen(stdin_fid, 'rb')
338+ sys.stdout = os.fdopen(stdout_fid, 'wb')
339+ sys.stderr = os.fdopen(stderr_fid, 'wb')
340+ ui.ui_factory.stdin = sys.stdin
341+ ui.ui_factory.stdout = sys.stdout
342+ ui.ui_factory.stderr = sys.stderr
343+ # Now that we've opened the handles, delete everything so that we don't
344+ # leave garbage around. Because the open() is done in blocking mode, we
345+ # know that someone has already connected to them, and we don't want
346+ # anyone else getting confused and connecting.
347+ # See [Decision #5]
348+ os.remove(stderr_path)
349+ os.remove(stdout_path)
350+ os.remove(stdin_path)
351+ os.rmdir(base_path)
352+
353+ def _close_child_file_descriptons(self):
354+ sys.stdin.close()
355+ sys.stderr.close()
356+ sys.stdout.close()
357+
358+ def become_child(self, command_argv, path):
359+ """We are in the spawned child code, do our magic voodoo."""
360+ # Stop tracking new signals
361+ self._unregister_signals()
362+ # Reset the start time
363+ trace._bzr_log_start_time = time.time()
364+ trace.mutter('%d starting %r'
365+ % (os.getpid(), command_argv,))
366+ self.host = None
367+ self.port = None
368+ self._sockname = None
369+ self._bind_child_file_descriptors(path)
370+ self._run_child_command(command_argv)
371+
372+ def _run_child_command(self, command_argv):
373+ # This is the point where we would actually want to do something with
374+ # our life
375+ # TODO: We may want to consider special-casing the 'lp-serve' command.
376+ # As that is the primary use-case for this service, it might be
377+ # interesting to have an already-instantiated instance, where we
378+ # can just pop on an extra argument and be ready to go. However,
379+ # that would probably only really be measurable if we prefork. As
380+ # it looks like ~200ms is 'fork()' time, but only 50ms is
381+ # run-the-command time.
382+ retcode = commands.run_bzr_catch_errors(command_argv)
383+ self._close_child_file_descriptons()
384+ trace.mutter('%d finished %r'
385+ % (os.getpid(), command_argv,))
386+ # We force os._exit() here, because we don't want to unwind the stack,
387+ # which has complex results. (We can get it to unwind back to the
388+ # cmd_launchpad_forking_service code, and even back to main() reporting
389+ # thereturn code, but after that, suddenly the return code changes from
390+ # a '0' to a '1', with no logging of info.
391+ # TODO: Should we call sys.exitfunc() here? it allows atexit functions
392+ # to fire, however, some of those may be still around from the
393+ # parent process, which we don't really want.
394+ sys.exitfunc()
395+ # See [Decision #6]
396+ os._exit(retcode)
397+
398+ @staticmethod
399+ def command_to_argv(command_str):
400+ """Convert a 'foo bar' style command to [u'foo', u'bar']"""
401+ # command_str must be a utf-8 string
402+ return [s.decode('utf-8') for s in shlex.split(command_str)]
403+
404+ @staticmethod
405+ def parse_env(env_str):
406+ """Convert the environment information into a dict.
407+
408+ :param env_str: A string full of environment variable declarations.
409+ Each key is simple ascii "key: value\n"
410+ The string must end with "end\n".
411+ :return: A dict of environment variables
412+ """
413+ env = {}
414+ if not env_str.endswith('end\n'):
415+ raise ValueError('Invalid env-str: %r' % (env_str,))
416+ env_str = env_str[:-5]
417+ if not env_str:
418+ return env
419+ env_entries = env_str.split('\n')
420+ for entry in env_entries:
421+ key, value = entry.split(': ', 1)
422+ env[key] = value
423+ return env
424+
425+ def fork_one_request(self, conn, client_addr, command_argv, env):
426+ """Fork myself and serve a request."""
427+ temp_name = tempfile.mkdtemp(prefix='lp-forking-service-child-')
428+ # Now that we've set everything up, send the response to the client we
429+ # create them first, so the client can start trying to connect to them,
430+ # while we fork and have the child do the same.
431+ self._children_spawned += 1
432+ pid = self._fork_function()
433+ if pid == 0:
434+ pid = os.getpid()
435+ trace.mutter('%d spawned' % (pid,))
436+ self._server_socket.close()
437+ for env_var, value in env.iteritems():
438+ osutils.set_or_unset_env(env_var, value)
439+ # See [Decision #3]
440+ self._create_child_file_descriptors(temp_name)
441+ conn.sendall('ok\n%d\n%s\n' % (pid, temp_name))
442+ conn.close()
443+ self.become_child(command_argv, temp_name)
444+ trace.warning('become_child returned!!!')
445+ sys.exit(1)
446+ else:
447+ self._child_processes[pid] = (temp_name, conn)
448+ self.log(client_addr, 'Spawned process %s for %r: %s'
449+ % (pid, command_argv, temp_name))
450+
451+ def main_loop(self):
452+ self._should_terminate.clear()
453+ self._register_signals()
454+ self._create_master_socket()
455+ trace.note('Listening on socket: %s' % (self.master_socket_path,))
456+ try:
457+ try:
458+ self._do_loop()
459+ finally:
460+ # Stop talking to others, we are shutting down
461+ self._cleanup_master_socket()
462+ except KeyboardInterrupt:
463+ # SIGINT received, try to shutdown cleanly
464+ pass
465+ trace.note('Shutting down. Waiting up to %.0fs for %d child processes'
466+ % (self.WAIT_FOR_CHILDREN_TIMEOUT,
467+ len(self._child_processes),))
468+ self._shutdown_children()
469+ trace.note('Exiting')
470+
471+ def _do_loop(self):
472+ while not self._should_terminate.isSet():
473+ try:
474+ conn, client_addr = self._server_socket.accept()
475+ except self._socket_timeout:
476+ pass # run shutdown and children checks
477+ except self._socket_error, e:
478+ if e.args[0] == errno.EINTR:
479+ pass # run shutdown and children checks
480+ elif e.args[0] != errno.EBADF:
481+ # We can get EBADF here while we are shutting down
482+ # So we just ignore it for now
483+ pass
484+ else:
485+ # Log any other failure mode
486+ trace.warning("listening socket error: %s", e)
487+ else:
488+ self.log(client_addr, 'connected')
489+ # TODO: We should probably trap exceptions coming out of this
490+ # and log them, so that we don't kill the service because
491+ # of an unhandled error
492+ # Note: settimeout is used so that a malformed request doesn't
493+ # cause us to hang forever. Note that the particular
494+ # implementation means that a malicious client could
495+ # probably send us one byte every Xms, and we would just
496+ # keep trying to read it. However, as a local service, we
497+ # aren't worrying about it.
498+ conn.settimeout(self.WAIT_FOR_REQUEST_TIMEOUT)
499+ try:
500+ self.serve_one_connection(conn, client_addr)
501+ except self._socket_timeout, e:
502+ trace.log_exception_quietly()
503+ self.log(client_addr, 'request timeout failure: %s' % (e,))
504+ conn.sendall('FAILURE\nrequest timed out\n')
505+ conn.close()
506+ self._poll_children()
507+
508+ def log(self, client_addr, message):
509+ """Log a message to the trace log.
510+
511+ Include the information about what connection is being served.
512+ """
513+ if client_addr is not None:
514+ # Note, we don't use conn.getpeername() because if a client
515+ # disconnects before we get here, that raises an exception
516+ conn_info = '[%s] ' % (client_addr,)
517+ else:
518+ conn_info = ''
519+ trace.mutter('%s%s' % (conn_info, message))
520+
521+ def log_information(self):
522+ """Log the status information.
523+
524+ This includes stuff like number of children, and ... ?
525+ """
526+ self._poll_children()
527+ self.log(None, 'Running for %.3fs' % (time.time() - self._start_time))
528+ self.log(None, '%d children currently running (spawned %d total)'
529+ % (len(self._child_processes), self._children_spawned))
530+ # Read the current information about memory consumption, etc.
531+ self.log(None, 'Self: %s'
532+ % (resource.getrusage(resource.RUSAGE_SELF),))
533+ # This seems to be the sum of all rusage for all children that have
534+ # been collected (not for currently running children, or ones we
535+ # haven't "wait"ed on.) We may want to read /proc/PID/status, since
536+ # 'live' information is probably more useful.
537+ self.log(None, 'Finished children: %s'
538+ % (resource.getrusage(resource.RUSAGE_CHILDREN),))
539+
540+ def _poll_children(self):
541+ """See if children are still running, etc.
542+
543+ One interesting hook here would be to track memory consumption, etc.
544+ """
545+ while self._child_processes:
546+ try:
547+ c_id, exit_code, rusage = os.wait3(os.WNOHANG)
548+ except OSError, e:
549+ if e.errno == errno.ECHILD:
550+ # TODO: We handle this right now because the test suite
551+ # fakes a child, since we wanted to test some code
552+ # without actually forking anything
553+ trace.mutter('_poll_children() called, and'
554+ ' self._child_processes indicates there are'
555+ ' children, but os.wait3() says there are not.'
556+ ' current_children: %s' % (self._child_processes,))
557+ return
558+ if c_id == 0:
559+ # No more children stopped right now
560+ return
561+ c_path, sock = self._child_processes.pop(c_id)
562+ trace.mutter('%s exited %s and usage: %s'
563+ % (c_id, exit_code, rusage))
564+ # See [Decision #4]
565+ try:
566+ sock.sendall('exited\n%s\n' % (exit_code,))
567+ except (self._socket_timeout, self._socket_error), e:
568+ # The client disconnected before we wanted them to,
569+ # no big deal
570+ trace.mutter('%s\'s socket already closed: %s' % (c_id, e))
571+ else:
572+ sock.close()
573+ if os.path.exists(c_path):
574+ # The child failed to cleanup after itself, do the work here
575+ trace.warning('Had to clean up after child %d: %s\n'
576+ % (c_id, c_path))
577+ shutil.rmtree(c_path, ignore_errors=True)
578+
579+ def _wait_for_children(self, secs):
580+ start = time.time()
581+ end = start + secs
582+ while self._child_processes:
583+ self._poll_children()
584+ if secs > 0 and time.time() > end:
585+ break
586+ time.sleep(self.SLEEP_FOR_CHILDREN_TIMEOUT)
587+
588+ def _shutdown_children(self):
589+ self._wait_for_children(self.WAIT_FOR_CHILDREN_TIMEOUT)
590+ if self._child_processes:
591+ trace.warning('Failed to stop children: %s'
592+ % ', '.join(map(str, self._child_processes)))
593+ for c_id in self._child_processes:
594+ trace.warning('sending SIGINT to %d' % (c_id,))
595+ os.kill(c_id, signal.SIGINT)
596+ # We sent the SIGINT signal, see if they exited
597+ self._wait_for_children(self.SLEEP_FOR_CHILDREN_TIMEOUT)
598+ if self._child_processes:
599+ # No? Then maybe something more powerful
600+ for c_id in self._child_processes:
601+ trace.warning('sending SIGKILL to %d' % (c_id,))
602+ os.kill(c_id, signal.SIGKILL)
603+ # We sent the SIGKILL signal, see if they exited
604+ self._wait_for_children(self.SLEEP_FOR_CHILDREN_TIMEOUT)
605+ if self._child_processes:
606+ for c_id, (c_path, sock) in self._child_processes.iteritems():
607+ # TODO: We should probably put something into this message?
608+ # However, the likelyhood is very small that this isn't
609+ # already closed because of SIGKILL + _wait_for_children
610+ # And I don't really know what to say...
611+ sock.close()
612+ if os.path.exists(c_path):
613+ trace.warning('Cleaning up after immortal child %d: %s\n'
614+ % (c_id, c_path))
615+ shutil.rmtree(c_path)
616+
617+ def _parse_fork_request(self, conn, client_addr, request):
618+ if request.startswith('fork-env '):
619+ while not request.endswith('end\n'):
620+ request += osutils.read_bytes_from_socket(conn)
621+ request = request.replace('\r\n', '\n')
622+ command, env = request[9:].split('\n', 1)
623+ else:
624+ command = request[5:].strip()
625+ env = 'end\n' # No env set
626+ try:
627+ command_argv = self.command_to_argv(command)
628+ env = self.parse_env(env)
629+ except Exception, e:
630+ # TODO: Log the traceback?
631+ self.log(client_addr, 'command or env parsing failed: %r'
632+ % (str(e),))
633+ conn.sendall('FAILURE\ncommand or env parsing failed: %r'
634+ % (str(e),))
635+ else:
636+ return command_argv, env
637+ return None, None
638+
639+ def serve_one_connection(self, conn, client_addr):
640+ request = ''
641+ while '\n' not in request:
642+ request += osutils.read_bytes_from_socket(conn)
643+ # telnet likes to use '\r\n' rather than '\n', and it is nice to have
644+ # an easy way to debug.
645+ request = request.replace('\r\n', '\n')
646+ self.log(client_addr, 'request: %r' % (request,))
647+ if request == 'hello\n':
648+ conn.sendall('ok\nyep, still alive\n')
649+ self.log_information()
650+ elif request == 'quit\n':
651+ self._should_terminate.set()
652+ conn.sendall('ok\nquit command requested... exiting\n')
653+ elif request.startswith('fork ') or request.startswith('fork-env '):
654+ command_argv, env = self._parse_fork_request(conn, client_addr,
655+ request)
656+ if command_argv is not None:
657+ # See [Decision #7]
658+ # TODO: Do we want to limit the number of children? And/or
659+ # prefork additional instances? (the design will need to
660+ # change if we prefork and run arbitrary commands.)
661+ self.fork_one_request(conn, client_addr, command_argv, env)
662+ # We don't close the conn like other code paths, since we use
663+ # it again later.
664+ return
665+ else:
666+ self.log(client_addr, 'FAILURE: unknown request: %r' % (request,))
667+ # See [Decision #8]
668+ conn.sendall('FAILURE\nunknown request: %r\n' % (request,))
669+ conn.close()
670+
671+
672+class cmd_launchpad_forking_service(Command):
673+ """Launch a long-running process, where you can ask for new processes.
674+
675+ The process will block on a given --port waiting for requests to be made.
676+ When a request is made, it will fork itself and redirect stdout/in/err to
677+ fifos on the filesystem, and start running the requested command. The
678+ caller will be informed where those file handles can be found. Thus it only
679+ makes sense that the process connecting to the port must be on the same
680+ system.
681+ """
682+
683+ aliases = ['lp-service']
684+
685+ takes_options = [Option('path',
686+ help='Listen for connections at PATH',
687+ type=str),
688+ Option('perms',
689+ help='Set the mode bits for the socket, interpreted'
690+ ' as an octal integer (same as chmod)'),
691+ Option('preload',
692+ help="Do/don't preload libraries before startup."),
693+ Option('children-timeout', type=int,
694+ help="Only wait XX seconds for children to exit"),
695+ ]
696+
697+ def _preload_libraries(self):
698+ global libraries_to_preload
699+ for pyname in libraries_to_preload:
700+ try:
701+ __import__(pyname)
702+ except ImportError, e:
703+ trace.mutter('failed to preload %s: %s' % (pyname, e))
704+
705+ def run(self, path=None, perms=None, preload=True,
706+ children_timeout=LPForkingService.WAIT_FOR_CHILDREN_TIMEOUT):
707+ if path is None:
708+ path = LPForkingService.DEFAULT_PATH
709+ if perms is None:
710+ perms = LPForkingService.DEFAULT_PERMISSIONS
711+ if preload:
712+ # We 'note' this because it often takes a fair amount of time.
713+ trace.note('Preloading %d modules' % (len(libraries_to_preload),))
714+ self._preload_libraries()
715+ service = LPForkingService(path, perms)
716+ service.WAIT_FOR_CHILDREN_TIMEOUT = children_timeout
717+ service.main_loop()
718+
719+register_command(cmd_launchpad_forking_service)
720+
721+
722+class cmd_launchpad_replay(Command):
723+ """Write input from stdin back to stdout or stderr.
724+
725+ This is a hidden command, primarily available for testing
726+ cmd_launchpad_forking_service.
727+ """
728+
729+ hidden = True
730+
731+ def run(self):
732+ # Just read line-by-line from stdin, and write out to stdout or stderr
733+ # depending on the prefix
734+ for line in sys.stdin:
735+ channel, contents = line.split(' ', 1)
736+ channel = int(channel)
737+ if channel == 1:
738+ sys.stdout.write(contents)
739+ sys.stdout.flush()
740+ elif channel == 2:
741+ sys.stderr.write(contents)
742+ sys.stderr.flush()
743+ else:
744+ raise RuntimeError('Invalid channel request.')
745+ return 0
746+
747+register_command(cmd_launchpad_replay)
748+
749+# This list was generated by run lsprofing a spawned child, and looking for
750+# <module ...> times, which indicate an import occured. Other possibilities are
751+# to just run "bzr lp-serve --profile-imports" manually, and observe what was
752+# expensive to import. It doesn't seem very easy to get this right
753+# automatically.
754+libraries_to_preload = [
755+ 'bzrlib.errors',
756+ 'bzrlib.repofmt.groupcompress_repo',
757+ 'bzrlib.repository',
758+ 'bzrlib.smart',
759+ 'bzrlib.smart.protocol',
760+ 'bzrlib.smart.request',
761+ 'bzrlib.smart.server',
762+ 'bzrlib.smart.vfs',
763+ 'bzrlib.transport.local',
764+ 'bzrlib.transport.readonly',
765+ 'lp.codehosting.bzrutils',
766+ 'lp.codehosting.vfs',
767+ 'lp.codehosting.vfs.branchfs',
768+ 'lp.codehosting.vfs.branchfsclient',
769+ 'lp.codehosting.vfs.hooks',
770+ 'lp.codehosting.vfs.transport',
771+ ]
772+
773+
774+
775+def load_tests(standard_tests, module, loader):
776+ standard_tests.addTests(loader.loadTestsFromModuleNames(
777+ [__name__ + '.' + x for x in [
778+ 'test_lpserve',
779+ ]]))
780+ return standard_tests
781
782=== added file 'bzrplugins/lpserve/test_lpserve.py'
783--- bzrplugins/lpserve/test_lpserve.py 1970-01-01 00:00:00 +0000
784+++ bzrplugins/lpserve/test_lpserve.py 2010-10-04 22:08:24 +0000
785@@ -0,0 +1,541 @@
786+# Copyright 2010 Canonical Ltd. This software is licensed under the
787+# GNU Affero General Public License version 3 (see the file LICENSE).
788+
789+import os
790+import signal
791+import socket
792+import subprocess
793+import tempfile
794+import threading
795+import time
796+
797+from testtools import content
798+
799+from bzrlib import (
800+ osutils,
801+ tests,
802+ trace,
803+ )
804+from bzrlib.plugins import lpserve
805+
806+from canonical.config import config
807+from lp.codehosting import get_bzr_path, get_BZR_PLUGIN_PATH_for_subprocess
808+
809+
810+class TestingLPForkingServiceInAThread(lpserve.LPForkingService):
811+ """Wrap starting and stopping an LPForkingService instance in a thread."""
812+
813+ # For testing, we set the timeouts much lower, because we want the tests to
814+ # run quickly
815+ WAIT_FOR_CHILDREN_TIMEOUT = 0.5
816+ SOCKET_TIMEOUT = 0.01
817+ SLEEP_FOR_CHILDREN_TIMEOUT = 0.01
818+ WAIT_FOR_REQUEST_TIMEOUT = 0.1
819+
820+ _fork_function = None
821+
822+ def __init__(self, path, perms=None):
823+ self.service_started = threading.Event()
824+ self.service_stopped = threading.Event()
825+ self.this_thread = None
826+ self.fork_log = []
827+ super(TestingLPForkingServiceInAThread, self).__init__(
828+ path=path, perms=None)
829+
830+ def _register_signals(self):
831+ pass # Don't register it for the test suite
832+
833+ def _unregister_signals(self):
834+ pass # We don't fork, and didn't register, so don't unregister
835+
836+ def _create_master_socket(self):
837+ super(TestingLPForkingServiceInAThread, self)._create_master_socket()
838+ self.service_started.set()
839+
840+ def main_loop(self):
841+ self.service_stopped.clear()
842+ super(TestingLPForkingServiceInAThread, self).main_loop()
843+ self.service_stopped.set()
844+
845+ def fork_one_request(self, conn, client_addr, command, env):
846+ # We intentionally don't allow the test suite to request a fork, as
847+ # threads + forks and everything else don't exactly play well together
848+ self.fork_log.append((command, env))
849+ conn.sendall('ok\nfake forking\n')
850+ conn.close()
851+
852+ @staticmethod
853+ def start_service(test):
854+ """Start a new LPForkingService in a thread at a random path.
855+
856+ This will block until the service has created its socket, and is ready
857+ to communicate.
858+
859+ :return: A new TestingLPForkingServiceInAThread instance
860+ """
861+ fd, path = tempfile.mkstemp(prefix='tmp-lp-forking-service-',
862+ suffix='.sock')
863+ # We don't want a temp file, we want a temp socket
864+ os.close(fd)
865+ os.remove(path)
866+ new_service = TestingLPForkingServiceInAThread(path=path)
867+ thread = threading.Thread(target=new_service.main_loop,
868+ name='TestingLPForkingServiceInAThread')
869+ new_service.this_thread = thread
870+ # should we be doing thread.setDaemon(True) ?
871+ thread.start()
872+ new_service.service_started.wait(10.0)
873+ if not new_service.service_started.isSet():
874+ raise RuntimeError(
875+ 'Failed to start the TestingLPForkingServiceInAThread')
876+ test.addCleanup(new_service.stop_service)
877+ # what about returning new_service._sockname ?
878+ return new_service
879+
880+ def stop_service(self):
881+ """Stop the test-server thread. This can be called multiple times."""
882+ if self.this_thread is None:
883+ # We already stopped the process
884+ return
885+ self._should_terminate.set()
886+ self.service_stopped.wait(10.0)
887+ if not self.service_stopped.isSet():
888+ raise RuntimeError(
889+ 'Failed to stop the TestingLPForkingServiceInAThread')
890+ self.this_thread.join()
891+ # Break any refcycles
892+ self.this_thread = None
893+
894+
895+class TestTestingLPForkingServiceInAThread(tests.TestCaseWithTransport):
896+
897+ def test_start_and_stop_service(self):
898+ service = TestingLPForkingServiceInAThread.start_service(self)
899+ service.stop_service()
900+
901+ def test_multiple_stops(self):
902+ service = TestingLPForkingServiceInAThread.start_service(self)
903+ service.stop_service()
904+ service.stop_service()
905+
906+ def test_autostop(self):
907+ # We shouldn't leak a thread here, as it should be part of the test
908+ # case teardown.
909+ service = TestingLPForkingServiceInAThread.start_service(self)
910+
911+
912+class TestCaseWithLPForkingService(tests.TestCaseWithTransport):
913+
914+ def setUp(self):
915+ super(TestCaseWithLPForkingService, self).setUp()
916+ self.service = TestingLPForkingServiceInAThread.start_service(self)
917+
918+ def send_message_to_service(self, message, one_byte_at_a_time=False):
919+ client_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
920+ client_sock.connect(self.service.master_socket_path)
921+ if one_byte_at_a_time:
922+ for byte in message:
923+ client_sock.send(byte)
924+ else:
925+ client_sock.sendall(message)
926+ response = client_sock.recv(1024)
927+ return response
928+
929+
930+class TestLPForkingServiceCommandToArgv(tests.TestCase):
931+
932+ def assertAsArgv(self, argv, command_str):
933+ self.assertEqual(argv,
934+ lpserve.LPForkingService.command_to_argv(command_str))
935+
936+ def test_simple(self):
937+ self.assertAsArgv([u'foo'], 'foo')
938+ self.assertAsArgv([u'foo', u'bar'], 'foo bar')
939+
940+ def test_quoted(self):
941+ self.assertAsArgv([u'foo'], 'foo')
942+ self.assertAsArgv([u'foo bar'], '"foo bar"')
943+
944+ def test_unicode(self):
945+ self.assertAsArgv([u'command', u'\xe5'], 'command \xc3\xa5')
946+
947+
948+class TestLPForkingServiceParseEnv(tests.TestCase):
949+
950+ def assertEnv(self, env, env_str):
951+ self.assertEqual(env, lpserve.LPForkingService.parse_env(env_str))
952+
953+ def assertInvalid(self, env_str):
954+ self.assertRaises(ValueError, lpserve.LPForkingService.parse_env,
955+ env_str)
956+
957+ def test_no_entries(self):
958+ self.assertEnv({}, 'end\n')
959+
960+ def test_one_entries(self):
961+ self.assertEnv({'BZR_EMAIL': 'joe@foo.com'},
962+ 'BZR_EMAIL: joe@foo.com\n'
963+ 'end\n')
964+
965+ def test_two_entries(self):
966+ self.assertEnv({'BZR_EMAIL': 'joe@foo.com', 'BAR': 'foo'},
967+ 'BZR_EMAIL: joe@foo.com\n'
968+ 'BAR: foo\n'
969+ 'end\n')
970+
971+ def test_invalid_empty(self):
972+ self.assertInvalid('')
973+
974+ def test_invalid_end(self):
975+ self.assertInvalid("BZR_EMAIL: joe@foo.com\n")
976+
977+ def test_invalid_entry(self):
978+ self.assertInvalid("BZR_EMAIL joe@foo.com\nend\n")
979+
980+
981+class TestLPForkingService(TestCaseWithLPForkingService):
982+
983+ def test_send_quit_message(self):
984+ response = self.send_message_to_service('quit\n')
985+ self.assertEqual('ok\nquit command requested... exiting\n', response)
986+ self.service.service_stopped.wait(10.0)
987+ self.assertTrue(self.service.service_stopped.isSet())
988+
989+ def test_send_invalid_message_fails(self):
990+ response = self.send_message_to_service('unknown\n')
991+ self.assertStartsWith(response, 'FAILURE')
992+
993+ def test_send_hello_heartbeat(self):
994+ response = self.send_message_to_service('hello\n')
995+ self.assertEqual('ok\nyep, still alive\n', response)
996+
997+ def test_hello_supports_crlf(self):
998+ # telnet seems to always write in text mode. It is nice to be able to
999+ # debug with simple telnet, so lets support it.
1000+ response = self.send_message_to_service('hello\r\n')
1001+ self.assertEqual('ok\nyep, still alive\n', response)
1002+
1003+ def test_send_simple_fork(self):
1004+ response = self.send_message_to_service('fork rocks\n')
1005+ self.assertEqual('ok\nfake forking\n', response)
1006+ self.assertEqual([(['rocks'], {})], self.service.fork_log)
1007+
1008+ def test_send_fork_env_with_empty_env(self):
1009+ response = self.send_message_to_service(
1010+ 'fork-env rocks\n'
1011+ 'end\n')
1012+ self.assertEqual('ok\nfake forking\n', response)
1013+ self.assertEqual([(['rocks'], {})], self.service.fork_log)
1014+
1015+ def test_send_fork_env_with_env(self):
1016+ response = self.send_message_to_service(
1017+ 'fork-env rocks\n'
1018+ 'BZR_EMAIL: joe@example.com\n'
1019+ 'end\n')
1020+ self.assertEqual('ok\nfake forking\n', response)
1021+ self.assertEqual([(['rocks'], {'BZR_EMAIL': 'joe@example.com'})],
1022+ self.service.fork_log)
1023+
1024+ def test_send_fork_env_slowly(self):
1025+ response = self.send_message_to_service(
1026+ 'fork-env rocks\n'
1027+ 'BZR_EMAIL: joe@example.com\n'
1028+ 'end\n', one_byte_at_a_time=True)
1029+ self.assertEqual('ok\nfake forking\n', response)
1030+ self.assertEqual([(['rocks'], {'BZR_EMAIL': 'joe@example.com'})],
1031+ self.service.fork_log)
1032+ # It should work with 'crlf' endings as well
1033+ response = self.send_message_to_service(
1034+ 'fork-env rocks\r\n'
1035+ 'BZR_EMAIL: joe@example.com\r\n'
1036+ 'end\r\n', one_byte_at_a_time=True)
1037+ self.assertEqual('ok\nfake forking\n', response)
1038+ self.assertEqual([(['rocks'], {'BZR_EMAIL': 'joe@example.com'}),
1039+ (['rocks'], {'BZR_EMAIL': 'joe@example.com'})],
1040+ self.service.fork_log)
1041+
1042+ def test_send_incomplete_fork_env_timeout(self):
1043+ # We should get a failure message if we can't quickly read the whole
1044+ # content
1045+ response = self.send_message_to_service(
1046+ 'fork-env rocks\n'
1047+ 'BZR_EMAIL: joe@example.com\n',
1048+ one_byte_at_a_time=True)
1049+ # Note that we *don't* send a final 'end\n'
1050+ self.assertStartsWith(response, 'FAILURE\n')
1051+
1052+ def test_send_incomplete_request_timeout(self):
1053+ # Requests end with '\n', send one without it
1054+ response = self.send_message_to_service('hello',
1055+ one_byte_at_a_time=True)
1056+ self.assertStartsWith(response, 'FAILURE\n')
1057+
1058+
1059+class TestCaseWithSubprocess(tests.TestCaseWithTransport):
1060+ """Override the bzr start_bzr_subprocess command.
1061+
1062+ The launchpad infrastructure requires a fair amount of configuration to get
1063+ paths, etc correct. So this provides that work.
1064+ """
1065+
1066+ def get_python_path(self):
1067+ """Return the path to the Python interpreter."""
1068+ return '%s/bin/py' % config.root
1069+
1070+ def start_bzr_subprocess(self, process_args, env_changes=None,
1071+ working_dir=None):
1072+ """Start bzr in a subprocess for testing.
1073+
1074+ Copied and modified from `bzrlib.tests.TestCase.start_bzr_subprocess`.
1075+ This version removes some of the skipping stuff, some of the
1076+ irrelevant comments (e.g. about win32) and uses Launchpad's own
1077+ mechanisms for getting the path to 'bzr'.
1078+
1079+ Comments starting with 'LAUNCHPAD' are comments about our
1080+ modifications.
1081+ """
1082+ if env_changes is None:
1083+ env_changes = {}
1084+ env_changes['BZR_PLUGIN_PATH'] = get_BZR_PLUGIN_PATH_for_subprocess()
1085+ old_env = {}
1086+
1087+ def cleanup_environment():
1088+ for env_var, value in env_changes.iteritems():
1089+ old_env[env_var] = osutils.set_or_unset_env(env_var, value)
1090+
1091+ def restore_environment():
1092+ for env_var, value in old_env.iteritems():
1093+ osutils.set_or_unset_env(env_var, value)
1094+
1095+ cwd = None
1096+ if working_dir is not None:
1097+ cwd = osutils.getcwd()
1098+ os.chdir(working_dir)
1099+
1100+ # LAUNCHPAD: Because of buildout, we need to get a custom Python
1101+ # binary, not sys.executable.
1102+ python_path = self.get_python_path()
1103+ # LAUNCHPAD: We can't use self.get_bzr_path(), since it'll find
1104+ # lib/bzrlib, rather than the path to sourcecode/bzr/bzr.
1105+ bzr_path = get_bzr_path()
1106+ try:
1107+ cleanup_environment()
1108+ command = [python_path, bzr_path]
1109+ command.extend(process_args)
1110+ process = self._popen(
1111+ command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1112+ stderr=subprocess.PIPE)
1113+ finally:
1114+ restore_environment()
1115+ if cwd is not None:
1116+ os.chdir(cwd)
1117+
1118+ return process
1119+
1120+
1121+class TestCaseWithLPForkingServiceSubprocess(TestCaseWithSubprocess):
1122+ """Tests will get a separate process to communicate to.
1123+
1124+ The number of these tests should be small, because it is expensive to start
1125+ and stop the daemon.
1126+
1127+ TODO: This should probably use testresources, or layers somehow...
1128+ """
1129+
1130+ def setUp(self):
1131+ super(TestCaseWithLPForkingServiceSubprocess, self).setUp()
1132+ self.service_process, self.service_path = self.start_service_subprocess()
1133+ self.addCleanup(self.stop_service)
1134+
1135+ def start_conversation(self, message, one_byte_at_a_time=False):
1136+ """Start talking to the service, and get the initial response."""
1137+ client_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1138+ trace.mutter('sending %r to socket %s' % (message, self.service_path))
1139+ client_sock.connect(self.service_path)
1140+ if one_byte_at_a_time:
1141+ for byte in message:
1142+ client_sock.send(byte)
1143+ else:
1144+ client_sock.sendall(message)
1145+ response = client_sock.recv(1024)
1146+ trace.mutter('response: %r' % (response,))
1147+ if response.startswith("FAILURE"):
1148+ raise RuntimeError('Failed to send message: %r' % (response,))
1149+ return response, client_sock
1150+
1151+ def send_message_to_service(self, message, one_byte_at_a_time=False):
1152+ response, client_sock = self.start_conversation(message,
1153+ one_byte_at_a_time=one_byte_at_a_time)
1154+ client_sock.close()
1155+ return response
1156+
1157+ def send_fork_request(self, command, env=None):
1158+ if env is not None:
1159+ request_lines = ['fork-env %s\n' % (command,)]
1160+ for key, value in env.iteritems():
1161+ request_lines.append('%s: %s\n' % (key, value))
1162+ request_lines.append('end\n')
1163+ request = ''.join(request_lines)
1164+ else:
1165+ request = 'fork %s\n' % (command,)
1166+ response, sock = self.start_conversation(request)
1167+ ok, pid, path, tail = response.split('\n')
1168+ self.assertEqual('ok', ok)
1169+ self.assertEqual('', tail)
1170+ # Don't really care what it is, but should be an integer
1171+ pid = int(pid)
1172+ path = path.strip()
1173+ self.assertContainsRe(path, '/lp-forking-service-child-')
1174+ return path, pid, sock
1175+
1176+ def start_service_subprocess(self):
1177+ # Make sure this plugin is exposed to the subprocess
1178+ # SLOOWWW (~2 seconds, which is why we are doing the work anyway)
1179+ fd, tempname = tempfile.mkstemp(prefix='tmp-log-bzr-lp-forking-')
1180+ # I'm not 100% sure about when cleanup runs versus addDetail, but I
1181+ # think this will work.
1182+ self.addCleanup(os.remove, tempname)
1183+ def read_log():
1184+ f = os.fdopen(fd)
1185+ f.seek(0)
1186+ content = f.read()
1187+ f.close()
1188+ return [content]
1189+ self.addDetail('server-log', content.Content(
1190+ content.ContentType('text', 'plain', {"charset": "utf8"}),
1191+ read_log))
1192+ service_fd, path = tempfile.mkstemp(prefix='tmp-lp-service-',
1193+ suffix='.sock')
1194+ os.close(service_fd)
1195+ os.remove(path) # service wants create it as a socket
1196+ env_changes = {'BZR_PLUGIN_PATH': lpserve.__path__[0],
1197+ 'BZR_LOG': tempname}
1198+ proc = self.start_bzr_subprocess(
1199+ ['lp-service', '--path', path, '--no-preload',
1200+ '--children-timeout=1'],
1201+ env_changes=env_changes)
1202+ trace.mutter('started lp-service subprocess')
1203+ # preload_line = proc.stderr.readline()
1204+ # self.assertStartsWith(preload_line, 'Preloading')
1205+ expected = 'Listening on socket: %s\n' % (path,)
1206+ path_line = proc.stderr.readline()
1207+ trace.mutter(path_line)
1208+ self.assertEqual(expected, path_line)
1209+ # The process won't delete it, so we do
1210+ return proc, path
1211+
1212+ def stop_service(self):
1213+ if self.service_process is None:
1214+ # Already stopped
1215+ return
1216+ # First, try to stop the service gracefully, by sending a 'quit'
1217+ # message
1218+ try:
1219+ response = self.send_message_to_service('quit\n')
1220+ except socket.error, e:
1221+ # Ignore a failure to connect, the service must be stopping/stopped
1222+ # already
1223+ response = None
1224+ tend = time.time() + 10.0
1225+ while self.service_process.poll() is None:
1226+ if time.time() > tend:
1227+ self.finish_bzr_subprocess(process=self.service_process,
1228+ send_signal=signal.SIGINT, retcode=3)
1229+ self.fail('Failed to quit gracefully after 10.0 seconds')
1230+ time.sleep(0.1)
1231+ if response is not None:
1232+ self.assertEqual('ok\nquit command requested... exiting\n',
1233+ response)
1234+
1235+ def _get_fork_handles(self, path):
1236+ trace.mutter('getting handles for: %s' % (path,))
1237+ stdin_path = os.path.join(path, 'stdin')
1238+ stdout_path = os.path.join(path, 'stdout')
1239+ stderr_path = os.path.join(path, 'stderr')
1240+ # Consider the ordering, the other side should open 'stdin' first, but
1241+ # we want it to block until we open the last one, or we race and it can
1242+ # delete the other handles before we get to open them.
1243+ child_stdin = open(stdin_path, 'wb')
1244+ child_stdout = open(stdout_path, 'rb')
1245+ child_stderr = open(stderr_path, 'rb')
1246+ return child_stdin, child_stdout, child_stderr
1247+
1248+ def communicate_with_fork(self, path, stdin=None):
1249+ child_stdin, child_stdout, child_stderr = self._get_fork_handles(path)
1250+ if stdin is not None:
1251+ child_stdin.write(stdin)
1252+ child_stdin.close()
1253+ stdout_content = child_stdout.read()
1254+ stderr_content = child_stderr.read()
1255+ return stdout_content, stderr_content
1256+
1257+ def assertReturnCode(self, expected_code, sock):
1258+ """Assert that we get the expected return code as a message."""
1259+ response = sock.recv(1024)
1260+ self.assertStartsWith(response, 'exited\n')
1261+ code = int(response.split('\n', 1)[1])
1262+ self.assertEqual(expected_code, code)
1263+
1264+ def test_fork_lp_serve_hello(self):
1265+ path, _, sock = self.send_fork_request('lp-serve --inet 2')
1266+ stdout_content, stderr_content = self.communicate_with_fork(path,
1267+ 'hello\n')
1268+ self.assertEqual('ok\x012\n', stdout_content)
1269+ self.assertEqual('', stderr_content)
1270+ self.assertReturnCode(0, sock)
1271+
1272+ def test_fork_replay(self):
1273+ path, _, sock = self.send_fork_request('launchpad-replay')
1274+ stdout_content, stderr_content = self.communicate_with_fork(path,
1275+ '1 hello\n2 goodbye\n1 maybe\n')
1276+ self.assertEqualDiff('hello\nmaybe\n', stdout_content)
1277+ self.assertEqualDiff('goodbye\n', stderr_content)
1278+ self.assertReturnCode(0, sock)
1279+
1280+ def test_just_run_service(self):
1281+ # Start and stop are defined in setUp()
1282+ pass
1283+
1284+ def test_fork_multiple_children(self):
1285+ paths = []
1286+ for idx in range(4):
1287+ paths.append(self.send_fork_request('launchpad-replay'))
1288+ # Do them out of order, as order shouldn't matter.
1289+ for idx in [3, 2, 0, 1]:
1290+ p, pid, sock = paths[idx]
1291+ stdout_msg = 'hello %d\n' % (idx,)
1292+ stderr_msg = 'goodbye %d\n' % (idx+1,)
1293+ stdout, stderr = self.communicate_with_fork(p,
1294+ '1 %s2 %s' % (stdout_msg, stderr_msg))
1295+ self.assertEqualDiff(stdout_msg, stdout)
1296+ self.assertEqualDiff(stderr_msg, stderr)
1297+ self.assertReturnCode(0, sock)
1298+
1299+ def test_fork_respects_env_vars(self):
1300+ path, pid, sock = self.send_fork_request('whoami',
1301+ env={'BZR_EMAIL': 'this_test@example.com'})
1302+ stdout_content, stderr_content = self.communicate_with_fork(path)
1303+ self.assertEqual('', stderr_content)
1304+ self.assertEqual('this_test@example.com\n', stdout_content)
1305+
1306+ def _check_exits_nicely(self, sig_id):
1307+ path, _, sock = self.send_fork_request('rocks')
1308+ self.assertEqual(None, self.service_process.poll())
1309+ # Now when we send SIGTERM, it should wait for the child to exit,
1310+ # before it tries to exit itself.
1311+ # In python2.6+ we could use self.service_process.terminate()
1312+ os.kill(self.service_process.pid, sig_id)
1313+ self.assertEqual(None, self.service_process.poll())
1314+ # Now talk to the child, so the service can close
1315+ stdout_content, stderr_content = self.communicate_with_fork(path)
1316+ self.assertEqual('It sure does!\n', stdout_content)
1317+ self.assertEqual('', stderr_content)
1318+ self.assertReturnCode(0, sock)
1319+ # And the process should exit cleanly
1320+ self.assertEqual(0, self.service_process.wait())
1321+
1322+ def test_sigterm_exits_nicely(self):
1323+ self._check_exits_nicely(signal.SIGTERM)
1324+
1325+ def test_sigint_exits_nicely(self):
1326+ self._check_exits_nicely(signal.SIGINT)
1327
1328=== modified file 'configs/development/launchpad-lazr.conf'
1329--- configs/development/launchpad-lazr.conf 2010-09-20 21:35:21 +0000
1330+++ configs/development/launchpad-lazr.conf 2010-10-04 22:08:24 +0000
1331@@ -76,6 +76,7 @@
1332 lp_url_hosts: dev
1333 access_log: /var/tmp/bazaar.launchpad.dev/codehosting-access.log
1334 blacklisted_hostnames:
1335+use_forking_daemon: True
1336
1337 [codeimport]
1338 bazaar_branch_store: file:///tmp/bazaar-branches
1339
1340=== modified file 'database/replication/Makefile'
1341--- database/replication/Makefile 2010-07-26 08:12:20 +0000
1342+++ database/replication/Makefile 2010-10-04 22:08:24 +0000
1343@@ -14,8 +14,9 @@
1344 # To test the staging rebuild script:
1345 #
1346 # $ cd database/replication
1347-# $ pg_dump --format=c launchpad_dev > launchpad.dump
1348-# $ make stagingsetup STAGING_CONFIG=dev-staging STAGING_DUMP=launchpad.dump
1349+# $ pg_dump --format=c launchpad_dev | bzip2 -c > launchpad.dump.bz2
1350+# $ make stagingsetup \
1351+# STAGING_CONFIG=dev-staging STAGING_DUMP=launchpad.dump.bz2
1352 # $ make stagingswitch STAGING_CONFIG=dev-staging
1353 #
1354 # To restore a dogfood database:
1355@@ -46,7 +47,12 @@
1356
1357 CREATEDB_83=createdb --encoding=UTF8
1358 CREATEDB_84=createdb --encoding=UTF8 --locale=C --template=template0
1359-CREATEDB=${CREATEDB_83}
1360+CREATEDB=${CREATEDB_84}
1361+
1362+# Set this to --exit-on-error once our dumps are coming from a PG 8.4
1363+# source. Currently, the PG 8.3 dumps generate some spurious errors
1364+# when being restored into a PG 8.4 database.
1365+EXIT_ON_ERROR=
1366
1367 # Turn off output silencing so we can see details of staging deployments.
1368 # Without the timestamps, we are unable to estimate production deployment
1369@@ -102,8 +108,9 @@
1370 # Restore the database. We need to restore permissions, despite
1371 # later running security.py, to pull in permissions granted on
1372 # production to users not maintained by security.py.
1373- bunzip2 --stdout ${STAGING_DUMP} | \
1374- pg_restore --dbname=lpmain_staging_new --no-owner --exit-on-error
1375+ # Stop ignoring error code after dumps come from an 8.4 system.
1376+ -bunzip2 --stdout ${STAGING_DUMP} | \
1377+ pg_restore --dbname=lpmain_staging_new --no-owner ${EXIT_ON_ERROR}
1378 # Uninstall Slony-I if it is installed - a pg_dump of a DB with
1379 # Slony-I installed isn't usable without this step.
1380 LPCONFIG=${NEW_STAGING_CONFIG} ./repair-restored-db.py
1381@@ -136,8 +143,9 @@
1382
1383 dogfood:
1384 ${CREATEDB} ${DOGFOOD_DBNAME}
1385- pg_restore --dbname=${DOGFOOD_DBNAME} --no-acl --no-owner \
1386- --exit-on-error ${DOGFOOD_DUMP}
1387+ # Stop ignoring error code after are dumps come from an 8.4 system.
1388+ -pg_restore --dbname=${DOGFOOD_DBNAME} --no-acl --no-owner \
1389+ ${EXIT_ON_ERROR} ${DOGFOOD_DUMP}
1390 ./repair-restored-db.py -d ${DOGFOOD_DBNAME}
1391 ../schema/upgrade.py -d ${DOGFOOD_DBNAME}
1392 ../schema/fti.py -d ${DOGFOOD_DBNAME}
1393
1394=== modified file 'database/replication/slon_ctl.py'
1395--- database/replication/slon_ctl.py 2010-05-19 18:07:56 +0000
1396+++ database/replication/slon_ctl.py 2010-10-04 22:08:24 +0000
1397@@ -88,9 +88,11 @@
1398
1399
1400 def get_logfile(nickname):
1401+ logdir = config.database.replication_logdir
1402+ if not os.path.isabs(logdir):
1403+ logdir = os.path.normpath(os.path.join(config.root, logdir))
1404 return os.path.join(
1405- config.root, 'database', 'replication',
1406- 'lpslon_%s_%s.log' % (nickname, config.instance_name))
1407+ logdir, 'lpslon_%s_%s.log' % (nickname, config.instance_name))
1408
1409
1410 def start(log, nodes, lag=None):
1411
1412=== modified file 'database/sampledata/current-dev.sql'
1413--- database/sampledata/current-dev.sql 2010-09-23 19:10:34 +0000
1414+++ database/sampledata/current-dev.sql 2010-10-04 22:08:24 +0000
1415@@ -1837,14 +1837,14 @@
1416
1417 ALTER TABLE distroarchseries DISABLE TRIGGER ALL;
1418
1419-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (1, 1, 1, 'i386', 1, true, 5, '2006-10-16 18:31:43.454475', true);
1420-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (6, 3, 1, 'i386', 1, true, 1, '2006-10-16 18:31:43.456532', true);
1421-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (7, 6, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457028', true);
1422-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (8, 10, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457484', true);
1423-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (9, 13, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457938', true);
1424-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (10, 13, 3, 'amd64', 1, true, 0, '2006-10-16 18:31:43.458434', true);
1425-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (11, 3, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.458892', false);
1426-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (12, 1, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.459349', false);
1427+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (1, 1, 1, 'i386', 1, true, 5, '2006-10-16 18:31:43.454475', true, true);
1428+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (6, 3, 1, 'i386', 1, true, 1, '2006-10-16 18:31:43.456532', true, true);
1429+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (7, 6, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457028', true, true);
1430+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (8, 10, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457484', true, true);
1431+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (9, 13, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457938', true, true);
1432+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (10, 13, 3, 'amd64', 1, true, 0, '2006-10-16 18:31:43.458434', true, true);
1433+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (11, 3, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.458892', false, true);
1434+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (12, 1, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.459349', false, true);
1435
1436
1437 ALTER TABLE distroarchseries ENABLE TRIGGER ALL;
1438@@ -3092,25 +3092,25 @@
1439
1440 ALTER TABLE branchrevision DISABLE TRIGGER ALL;
1441
1442-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (1, 1, 10, 1);
1443-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (2, 1, 11, 2);
1444-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (3, 1, 12, 3);
1445-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (4, 1, 20, 4);
1446-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (5, 2, 20, 5);
1447-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (6, 3, 20, 6);
1448-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (7, 4, 20, 7);
1449-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (8, 5, 20, 8);
1450-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (9, 6, 20, 9);
1451-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (10, 1, 21, 4);
1452-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (11, 2, 21, 5);
1453-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (12, 3, 21, 10);
1454-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (13, 4, 21, 11);
1455-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (14, 5, 21, 8);
1456-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (15, 6, 21, 9);
1457-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (16, NULL, 20, 10);
1458-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (17, NULL, 20, 11);
1459-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (18, NULL, 21, 6);
1460-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (19, NULL, 21, 7);
1461+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 10, 1);
1462+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 11, 2);
1463+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 12, 3);
1464+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 20, 4);
1465+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 21, 4);
1466+INSERT INTO branchrevision (sequence, branch, revision) VALUES (2, 20, 5);
1467+INSERT INTO branchrevision (sequence, branch, revision) VALUES (2, 21, 5);
1468+INSERT INTO branchrevision (sequence, branch, revision) VALUES (3, 20, 6);
1469+INSERT INTO branchrevision (sequence, branch, revision) VALUES (3, 21, 10);
1470+INSERT INTO branchrevision (sequence, branch, revision) VALUES (4, 20, 7);
1471+INSERT INTO branchrevision (sequence, branch, revision) VALUES (4, 21, 11);
1472+INSERT INTO branchrevision (sequence, branch, revision) VALUES (5, 20, 8);
1473+INSERT INTO branchrevision (sequence, branch, revision) VALUES (5, 21, 8);
1474+INSERT INTO branchrevision (sequence, branch, revision) VALUES (6, 20, 9);
1475+INSERT INTO branchrevision (sequence, branch, revision) VALUES (6, 21, 9);
1476+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 20, 10);
1477+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 20, 11);
1478+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 21, 6);
1479+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 21, 7);
1480
1481
1482 ALTER TABLE branchrevision ENABLE TRIGGER ALL;
1483@@ -3333,42 +3333,42 @@
1484
1485 ALTER TABLE bugmessage DISABLE TRIGGER ALL;
1486
1487-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (1, 2, 1, NULL, NULL, true);
1488-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (2, 1, 3, NULL, NULL, true);
1489-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (3, 1, 4, NULL, NULL, true);
1490-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (4, 2, 5, NULL, NULL, true);
1491-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (5, 2, 6, NULL, NULL, true);
1492-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (6, 4, 7, NULL, NULL, true);
1493-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (7, 5, 8, NULL, NULL, true);
1494-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (8, 6, 9, NULL, NULL, true);
1495-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (9, 3, 10, NULL, NULL, true);
1496-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (10, 7, 11, NULL, NULL, true);
1497-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (11, 8, 14, NULL, NULL, true);
1498-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (12, 9, 15, NULL, NULL, true);
1499-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (13, 10, 17, NULL, NULL, true);
1500-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (14, 10, 16, NULL, NULL, true);
1501-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (15, 11, 24, NULL, NULL, true);
1502-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (16, 11, 25, NULL, NULL, true);
1503-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (17, 11, 26, NULL, NULL, true);
1504-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (18, 11, 27, NULL, NULL, true);
1505-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (19, 11, 28, NULL, NULL, true);
1506-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (20, 11, 29, NULL, NULL, true);
1507-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (21, 11, 30, NULL, NULL, true);
1508-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (22, 12, 31, NULL, NULL, true);
1509-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (23, 12, 33, NULL, NULL, true);
1510-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (24, 12, 34, NULL, NULL, true);
1511-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (25, 12, 35, NULL, NULL, true);
1512-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (26, 12, 36, NULL, NULL, true);
1513-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (27, 13, 37, NULL, NULL, true);
1514-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (28, 13, 38, NULL, NULL, true);
1515-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (29, 14, 39, NULL, NULL, true);
1516-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (30, 15, 40, NULL, NULL, true);
1517-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (31, 15, 44, 11, '<4284D7D1.6010208@gmx.de>', true);
1518-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (32, 15, 45, 11, '<20050517185429.GB20786@spring.luon.net>', true);
1519-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (33, 15, 46, 11, '<428A44E9.6090802@gmx.de>', true);
1520-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (34, 15, 47, 11, '<20050517202044.GA23231@spring.luon.net>', true);
1521-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (35, 15, 48, 11, '<20050617140011.GA15638@piware.de>', true);
1522-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (36, 15, 49, 11, '<42BD2E36.9090809@gmx.de>', true);
1523+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (1, 2, 1, NULL, NULL, true, NULL);
1524+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (2, 1, 3, NULL, NULL, true, NULL);
1525+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (3, 1, 4, NULL, NULL, true, NULL);
1526+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (4, 2, 5, NULL, NULL, true, NULL);
1527+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (5, 2, 6, NULL, NULL, true, NULL);
1528+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (6, 4, 7, NULL, NULL, true, NULL);
1529+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (7, 5, 8, NULL, NULL, true, NULL);
1530+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (8, 6, 9, NULL, NULL, true, NULL);
1531+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (9, 3, 10, NULL, NULL, true, NULL);
1532+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (10, 7, 11, NULL, NULL, true, NULL);
1533+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (11, 8, 14, NULL, NULL, true, NULL);
1534+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (12, 9, 15, NULL, NULL, true, NULL);
1535+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (13, 10, 17, NULL, NULL, true, NULL);
1536+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (14, 10, 16, NULL, NULL, true, NULL);
1537+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (15, 11, 24, NULL, NULL, true, NULL);
1538+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (16, 11, 25, NULL, NULL, true, NULL);
1539+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (17, 11, 26, NULL, NULL, true, NULL);
1540+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (18, 11, 27, NULL, NULL, true, NULL);
1541+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (19, 11, 28, NULL, NULL, true, NULL);
1542+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (20, 11, 29, NULL, NULL, true, NULL);
1543+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (21, 11, 30, NULL, NULL, true, NULL);
1544+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (22, 12, 31, NULL, NULL, true, NULL);
1545+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (23, 12, 33, NULL, NULL, true, NULL);
1546+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (24, 12, 34, NULL, NULL, true, NULL);
1547+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (25, 12, 35, NULL, NULL, true, NULL);
1548+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (26, 12, 36, NULL, NULL, true, NULL);
1549+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (27, 13, 37, NULL, NULL, true, NULL);
1550+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (28, 13, 38, NULL, NULL, true, NULL);
1551+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (29, 14, 39, NULL, NULL, true, NULL);
1552+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (30, 15, 40, NULL, NULL, true, NULL);
1553+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (31, 15, 44, 11, '<4284D7D1.6010208@gmx.de>', true, NULL);
1554+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (32, 15, 45, 11, '<20050517185429.GB20786@spring.luon.net>', true, NULL);
1555+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (33, 15, 46, 11, '<428A44E9.6090802@gmx.de>', true, NULL);
1556+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (34, 15, 47, 11, '<20050517202044.GA23231@spring.luon.net>', true, NULL);
1557+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (35, 15, 48, 11, '<20050617140011.GA15638@piware.de>', true, NULL);
1558+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (36, 15, 49, 11, '<42BD2E36.9090809@gmx.de>', true, NULL);
1559
1560
1561 ALTER TABLE bugmessage ENABLE TRIGGER ALL;
1562@@ -3792,6 +3792,27 @@
1563 ALTER TABLE bugtrackeralias ENABLE TRIGGER ALL;
1564
1565
1566+ALTER TABLE bugtrackercomponentgroup DISABLE TRIGGER ALL;
1567+
1568+
1569+
1570+ALTER TABLE bugtrackercomponentgroup ENABLE TRIGGER ALL;
1571+
1572+
1573+ALTER TABLE distributionsourcepackage DISABLE TRIGGER ALL;
1574+
1575+
1576+
1577+ALTER TABLE distributionsourcepackage ENABLE TRIGGER ALL;
1578+
1579+
1580+ALTER TABLE bugtrackercomponent DISABLE TRIGGER ALL;
1581+
1582+
1583+
1584+ALTER TABLE bugtrackercomponent ENABLE TRIGGER ALL;
1585+
1586+
1587 ALTER TABLE bugtrackerperson DISABLE TRIGGER ALL;
1588
1589
1590@@ -3993,6 +4014,13 @@
1591 ALTER TABLE distributionbounty ENABLE TRIGGER ALL;
1592
1593
1594+ALTER TABLE distributionjob DISABLE TRIGGER ALL;
1595+
1596+
1597+
1598+ALTER TABLE distributionjob ENABLE TRIGGER ALL;
1599+
1600+
1601 ALTER TABLE distributionmirror DISABLE TRIGGER ALL;
1602
1603 INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer, country_dns_mirror) VALUES (1, 1, 'archive-mirror', 'http://localhost:11375/valid-mirror/', NULL, NULL, NULL, NULL, 1, 10, 75, 1, true, true, '2006-10-16 18:31:43.434567', NULL, 30, NULL, NULL, false);
1604@@ -4010,13 +4038,6 @@
1605 ALTER TABLE distributionmirror ENABLE TRIGGER ALL;
1606
1607
1608-ALTER TABLE distributionsourcepackage DISABLE TRIGGER ALL;
1609-
1610-
1611-
1612-ALTER TABLE distributionsourcepackage ENABLE TRIGGER ALL;
1613-
1614-
1615 ALTER TABLE distributionsourcepackagecache DISABLE TRIGGER ALL;
1616
1617 INSERT INTO distributionsourcepackagecache (id, distribution, sourcepackagename, name, binpkgnames, binpkgsummaries, binpkgdescriptions, fti, changelog, archive) VALUES (1, 3, 19, 'alsa-utils', '', '', '', NULL, NULL, 1);
1618@@ -4567,6 +4588,13 @@
1619 ALTER TABLE hwtestanswerdevice ENABLE TRIGGER ALL;
1620
1621
1622+ALTER TABLE incrementaldiff DISABLE TRIGGER ALL;
1623+
1624+
1625+
1626+ALTER TABLE incrementaldiff ENABLE TRIGGER ALL;
1627+
1628+
1629 ALTER TABLE ircid DISABLE TRIGGER ALL;
1630
1631 INSERT INTO ircid (id, person, network, nickname) VALUES (1, 1, 'irc.freenode.net', 'mark');
1632@@ -11141,6 +11169,13 @@
1633 ALTER TABLE translationtemplateitem ENABLE TRIGGER ALL;
1634
1635
1636+ALTER TABLE translationtemplatesbuild DISABLE TRIGGER ALL;
1637+
1638+
1639+
1640+ALTER TABLE translationtemplatesbuild ENABLE TRIGGER ALL;
1641+
1642+
1643 ALTER TABLE translator DISABLE TRIGGER ALL;
1644
1645 INSERT INTO translator (id, translationgroup, language, translator, datecreated, style_guide_url) VALUES (1, 1, 387, 53, '2005-07-13 13:14:19.748396', NULL);
1646
1647=== modified file 'database/sampledata/current.sql'
1648--- database/sampledata/current.sql 2010-09-04 20:38:35 +0000
1649+++ database/sampledata/current.sql 2010-10-04 22:08:24 +0000
1650@@ -792,6 +792,36 @@
1651
1652
1653
1654+
1655+
1656+
1657+
1658+
1659+
1660+
1661+
1662+
1663+
1664+
1665+
1666+
1667+
1668+
1669+
1670+
1671+
1672+
1673+
1674+
1675+
1676+
1677+
1678+
1679+
1680+
1681+
1682+
1683+
1684 SET SESSION AUTHORIZATION DEFAULT;
1685
1686 ALTER TABLE account DISABLE TRIGGER ALL;
1687@@ -1819,14 +1849,14 @@
1688
1689 ALTER TABLE distroarchseries DISABLE TRIGGER ALL;
1690
1691-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (1, 1, 1, 'i386', 1, true, 5, '2006-10-16 18:31:43.454475', true);
1692-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (6, 3, 1, 'i386', 1, true, 1, '2006-10-16 18:31:43.456532', true);
1693-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (7, 6, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457028', true);
1694-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (8, 10, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457484', true);
1695-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (9, 13, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457938', true);
1696-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (10, 13, 3, 'amd64', 1, true, 0, '2006-10-16 18:31:43.458434', true);
1697-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (11, 3, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.458892', false);
1698-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (12, 1, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.459349', false);
1699+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (1, 1, 1, 'i386', 1, true, 5, '2006-10-16 18:31:43.454475', true, true);
1700+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (6, 3, 1, 'i386', 1, true, 1, '2006-10-16 18:31:43.456532', true, true);
1701+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (7, 6, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457028', true, true);
1702+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (8, 10, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457484', true, true);
1703+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (9, 13, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457938', true, true);
1704+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (10, 13, 3, 'amd64', 1, true, 0, '2006-10-16 18:31:43.458434', true, true);
1705+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (11, 3, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.458892', false, true);
1706+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (12, 1, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.459349', false, true);
1707
1708
1709 ALTER TABLE distroarchseries ENABLE TRIGGER ALL;
1710@@ -2238,8 +2268,8 @@
1711
1712 ALTER TABLE builder DISABLE TRIGGER ALL;
1713
1714-INSERT INTO builder (id, processor, name, title, description, owner, speedindex, builderok, failnotes, virtualized, url, manual, date_created, vm_host, active) VALUES (1, 1, 'bob', 'Bob The Builder', 'The default build-slave', 61, NULL, true, NULL, false, 'http://localhost:8221/', false, '2006-10-16 18:31:43.226724', NULL, true);
1715-INSERT INTO builder (id, processor, name, title, description, owner, speedindex, builderok, failnotes, virtualized, url, manual, date_created, vm_host, active) VALUES (2, 1, 'frog', 'The frog builder', 'The untrusted build-slave', 61, NULL, false, NULL, true, 'http://localhost:9221/', false, '2006-10-31 18:31:43.226724', 'localhost-host.ppa', true);
1716+INSERT INTO builder (id, processor, name, title, description, owner, speedindex, builderok, failnotes, virtualized, url, manual, date_created, vm_host, active, failure_count) VALUES (1, 1, 'bob', 'Bob The Builder', 'The default build-slave', 61, NULL, true, NULL, false, 'http://localhost:8221/', false, '2006-10-16 18:31:43.226724', NULL, true, 0);
1717+INSERT INTO builder (id, processor, name, title, description, owner, speedindex, builderok, failnotes, virtualized, url, manual, date_created, vm_host, active, failure_count) VALUES (2, 1, 'frog', 'The frog builder', 'The untrusted build-slave', 61, NULL, false, NULL, true, 'http://localhost:9221/', false, '2006-10-31 18:31:43.226724', 'localhost-host.ppa', true, 0);
1718
1719
1720 ALTER TABLE builder ENABLE TRIGGER ALL;
1721@@ -2247,30 +2277,30 @@
1722
1723 ALTER TABLE buildfarmjob DISABLE TRIGGER ALL;
1724
1725-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (2, 1, false, '2004-09-27 11:57:13', '2004-09-27 11:55:13', '2004-09-27 11:57:14', NULL, 1, 1, 1, 1);
1726-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (6, 1, false, '2006-12-01 00:00:00', '2006-12-01 00:00:00', '2006-12-01 00:00:01', NULL, 1, 2, 1, 1);
1727-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (7, 1, false, '2005-03-24 00:00:00', '2005-03-24 23:58:43', '2005-03-25 00:00:03', NULL, 1, 1, 1, 1);
1728-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (8, 1, false, '2005-09-30 00:00:00', NULL, NULL, NULL, NULL, 6, NULL, 1);
1729-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (9, 1, false, '2005-10-01 00:00:00', '2005-10-01 23:56:41', '2005-10-02 00:00:01', NULL, 1, 2, 1, 1);
1730-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (10, 1, false, '2006-01-27 00:00:00', NULL, NULL, NULL, NULL, 1, NULL, 1);
1731-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (11, 1, false, '2006-02-14 00:00:00', NULL, NULL, NULL, NULL, 0, NULL, 1);
1732-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (12, 1, false, '2006-02-28 00:00:00', '2006-02-27 23:53:59', '2006-02-28 00:00:01', NULL, 1, 3, 1, 1);
1733-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (13, 1, false, '2006-03-21 00:00:00', '2006-03-21 00:58:33', '2006-03-21 01:00:03', NULL, 1, 5, 1, 1);
1734-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (14, 1, false, '2006-03-22 00:00:00', '2006-03-21 00:58:32', '2006-03-21 01:00:02', NULL, 1, 5, 1, 1);
1735-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (15, 1, false, '2006-03-22 00:00:01', '2006-03-21 00:58:30', '2006-03-21 01:00:00', NULL, 1, 5, 1, 1);
1736-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (16, 1, false, '2005-03-24 00:00:01', '2005-03-24 23:58:42', '2005-03-25 00:00:02', NULL, 1, 1, 1, 1);
1737-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (18, 1, false, '2004-09-27 11:57:14', '2004-09-27 11:55:12', '2004-09-27 11:57:13', NULL, 1, 1, 1, 1);
1738-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (19, 1, false, '2005-03-24 00:00:02', '2005-03-24 23:58:41', '2005-03-25 00:00:01', NULL, 1, 1, 1, 1);
1739-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (21, 1, false, '2006-12-01 00:00:01', NULL, NULL, NULL, NULL, 2, NULL, 1);
1740-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (22, 1, false, '2007-04-20 00:00:00', '2007-04-19 23:58:41', '2007-04-20 00:00:01', NULL, 1, 7, 1, 1);
1741-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (23, 1, false, '2006-04-11 12:00:00', NULL, NULL, NULL, NULL, 1, NULL, 1);
1742-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (24, 1, true, '2007-05-30 00:00:00', '2007-05-29 23:58:41', '2007-05-30 00:00:01', NULL, 1, 2, 1, 1);
1743-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (25, 1, true, '2007-07-08 12:00:00', NULL, NULL, NULL, NULL, 1, NULL, 1);
1744-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (26, 1, true, '2007-07-08 00:00:00', '2007-07-07 23:58:41', '2007-07-08 00:00:01', NULL, 1, 2, 1, 1);
1745-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (27, 1, true, '2007-07-24 00:00:00', '2007-07-23 23:58:41', '2007-07-24 00:00:01', NULL, 1, 1, 1, 1);
1746-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (28, 3, true, '2007-08-10 00:00:00', '2007-08-10 00:00:00', '2007-08-10 00:00:13', NULL, 1, 1, 1, 1);
1747-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (29, 1, false, '2007-08-09 21:54:18.553132', '2007-08-09 23:49:59', '2007-08-09 23:59:59', NULL, NULL, 1, NULL, 1);
1748-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (30, 3, false, '2007-08-10 00:00:01', '2007-08-10 00:00:01', '2007-08-10 00:00:14', NULL, 1, 1, 1, 1);
1749+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (2, 1, false, '2004-09-27 11:57:13', '2004-09-27 11:55:13', '2004-09-27 11:57:14', NULL, 1, 1, 1, 1, 0);
1750+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (6, 1, false, '2006-12-01 00:00:00', '2006-12-01 00:00:00', '2006-12-01 00:00:01', NULL, 1, 2, 1, 1, 0);
1751+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (7, 1, false, '2005-03-24 00:00:00', '2005-03-24 23:58:43', '2005-03-25 00:00:03', NULL, 1, 1, 1, 1, 0);
1752+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (8, 1, false, '2005-09-30 00:00:00', NULL, NULL, NULL, NULL, 6, NULL, 1, 0);
1753+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (9, 1, false, '2005-10-01 00:00:00', '2005-10-01 23:56:41', '2005-10-02 00:00:01', NULL, 1, 2, 1, 1, 0);
1754+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (10, 1, false, '2006-01-27 00:00:00', NULL, NULL, NULL, NULL, 1, NULL, 1, 0);
1755+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (11, 1, false, '2006-02-14 00:00:00', NULL, NULL, NULL, NULL, 0, NULL, 1, 0);
1756+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (12, 1, false, '2006-02-28 00:00:00', '2006-02-27 23:53:59', '2006-02-28 00:00:01', NULL, 1, 3, 1, 1, 0);
1757+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (13, 1, false, '2006-03-21 00:00:00', '2006-03-21 00:58:33', '2006-03-21 01:00:03', NULL, 1, 5, 1, 1, 0);
1758+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (14, 1, false, '2006-03-22 00:00:00', '2006-03-21 00:58:32', '2006-03-21 01:00:02', NULL, 1, 5, 1, 1, 0);
1759+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (15, 1, false, '2006-03-22 00:00:01', '2006-03-21 00:58:30', '2006-03-21 01:00:00', NULL, 1, 5, 1, 1, 0);
1760+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (16, 1, false, '2005-03-24 00:00:01', '2005-03-24 23:58:42', '2005-03-25 00:00:02', NULL, 1, 1, 1, 1, 0);
1761+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (18, 1, false, '2004-09-27 11:57:14', '2004-09-27 11:55:12', '2004-09-27 11:57:13', NULL, 1, 1, 1, 1, 0);
1762+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (19, 1, false, '2005-03-24 00:00:02', '2005-03-24 23:58:41', '2005-03-25 00:00:01', NULL, 1, 1, 1, 1, 0);
1763+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (21, 1, false, '2006-12-01 00:00:01', NULL, NULL, NULL, NULL, 2, NULL, 1, 0);
1764+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (22, 1, false, '2007-04-20 00:00:00', '2007-04-19 23:58:41', '2007-04-20 00:00:01', NULL, 1, 7, 1, 1, 0);
1765+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (23, 1, false, '2006-04-11 12:00:00', NULL, NULL, NULL, NULL, 1, NULL, 1, 0);
1766+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (24, 1, true, '2007-05-30 00:00:00', '2007-05-29 23:58:41', '2007-05-30 00:00:01', NULL, 1, 2, 1, 1, 0);
1767+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (25, 1, true, '2007-07-08 12:00:00', NULL, NULL, NULL, NULL, 1, NULL, 1, 0);
1768+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (26, 1, true, '2007-07-08 00:00:00', '2007-07-07 23:58:41', '2007-07-08 00:00:01', NULL, 1, 2, 1, 1, 0);
1769+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (27, 1, true, '2007-07-24 00:00:00', '2007-07-23 23:58:41', '2007-07-24 00:00:01', NULL, 1, 1, 1, 1, 0);
1770+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (28, 3, true, '2007-08-10 00:00:00', '2007-08-10 00:00:00', '2007-08-10 00:00:13', NULL, 1, 1, 1, 1, 0);
1771+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (29, 1, false, '2007-08-09 21:54:18.553132', '2007-08-09 23:49:59', '2007-08-09 23:59:59', NULL, NULL, 1, NULL, 1, 0);
1772+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (30, 3, false, '2007-08-10 00:00:01', '2007-08-10 00:00:01', '2007-08-10 00:00:14', NULL, 1, 1, 1, 1, 0);
1773
1774
1775 ALTER TABLE buildfarmjob ENABLE TRIGGER ALL;
1776@@ -2365,29 +2395,29 @@
1777
1778 ALTER TABLE sourcepackagerelease DISABLE TRIGGER ALL;
1779
1780-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (14, 1, '0.9', '2004-09-27 11:57:13', 1, NULL, 1, 'Mozilla dummy Changelog......', 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1), pmount', 'bacula-common (= 1.34.6-2), bacula-director-common (= 1.34.6-2), postgresql-client (>= 7.4), pmount', 'any', NULL, 1, 1, 1, 1, 1, 'Mark Shuttleworth <mark@canonical.com>', '3.6.2', '1.0', 'mozilla-firefox', 1, NULL, 'gcc-4.0, pmount', 'gcc-4.0-base, pmount', NULL, NULL, NULL);
1781-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (15, 1, '1.0', '2004-09-27 11:57:13', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 2, 1, 9, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1782-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (16, 1, '1.0-1', '2005-03-10 16:30:00', 1, NULL, 1, NULL, NULL, NULL, 'any', NULL, 3, 1, 10, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1783-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (17, 1, '0.99.6-1', '2005-03-14 18:00:00', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 2, 1, 10, 1, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1784-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (20, 1, '0.1-1', '2005-03-24 20:59:31.439579', 1, NULL, 1, 'pmount (0.1-1) hoary; urgency=low
1785+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (14, 1, '0.9', '2004-09-27 11:57:13', 1, NULL, 1, 'Mozilla dummy Changelog......', 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1), pmount', 'bacula-common (= 1.34.6-2), bacula-director-common (= 1.34.6-2), postgresql-client (>= 7.4), pmount', 'any', NULL, 1, 1, 1, 1, 1, 'Mark Shuttleworth <mark@canonical.com>', '3.6.2', '1.0', 'mozilla-firefox', 1, NULL, 'gcc-4.0, pmount', 'gcc-4.0-base, pmount', NULL, NULL, NULL, NULL);
1786+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (15, 1, '1.0', '2004-09-27 11:57:13', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 2, 1, 9, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1787+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (16, 1, '1.0-1', '2005-03-10 16:30:00', 1, NULL, 1, NULL, NULL, NULL, 'any', NULL, 3, 1, 10, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1788+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (17, 1, '0.99.6-1', '2005-03-14 18:00:00', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 2, 1, 10, 1, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1789+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (20, 1, '0.1-1', '2005-03-24 20:59:31.439579', 1, NULL, 1, 'pmount (0.1-1) hoary; urgency=low
1790
1791 * Fix description (Malone #1)
1792 * Fix debian (Debian #2000)
1793 * Fix warty (Warty Ubuntu #1)
1794
1795- -- Sample Person <test@canonical.com> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'all', NULL, 2, 1, 14, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1796-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (21, 1, '0.1-2', '2005-06-24 20:59:31.439579', 1, NULL, 1, 'This is a placeholder changelog for pmount 0.1-2', NULL, NULL, 'powerpc', NULL, 1, 1, 14, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1797-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (23, 1, '1.0.8-1ubuntu1', '2005-02-03 08:50:00', 1, NULL, 1, 'alsa-utils (1.0.8-1ubuntu1) warty; urgency=low
1798-
1799- * Placeholder
1800-
1801- -- Sample Person <test@canonical.com> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'all', NULL, 1, 1, 19, 1, 1, 'Mark Shuttleworth <mark@example.com>', '3.6.2', '1.0', 'alsa-mixer', 1, NULL, NULL, NULL, NULL, NULL, NULL);
1802-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (24, 1, '1.0.9a-4', '2005-07-01 22:47:00', 1, NULL, 1, 'alsa-utils (1.0.9a-4) warty; urgency=low
1803-
1804- * Placeholder
1805-
1806- -- Sample Person <test@canonical.com> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'any', NULL, 2, 1, 19, 8, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1807-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (25, 1, '1.0.9a-4ubuntu1', '2005-08-01 14:10:00', 1, NULL, 1, 'alsa-utils (1.0.9a-4ubuntu1) hoary; urgency=low
1808+ -- Sample Person <test@canonical.com> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'all', NULL, 2, 1, 14, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1809+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (21, 1, '0.1-2', '2005-06-24 20:59:31.439579', 1, NULL, 1, 'This is a placeholder changelog for pmount 0.1-2', NULL, NULL, 'powerpc', NULL, 1, 1, 14, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1810+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (23, 1, '1.0.8-1ubuntu1', '2005-02-03 08:50:00', 1, NULL, 1, 'alsa-utils (1.0.8-1ubuntu1) warty; urgency=low
1811+
1812+ * Placeholder
1813+
1814+ -- Sample Person <test@canonical.com> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'all', NULL, 1, 1, 19, 1, 1, 'Mark Shuttleworth <mark@example.com>', '3.6.2', '1.0', 'alsa-mixer', 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1815+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (24, 1, '1.0.9a-4', '2005-07-01 22:47:00', 1, NULL, 1, 'alsa-utils (1.0.9a-4) warty; urgency=low
1816+
1817+ * Placeholder
1818+
1819+ -- Sample Person <test@canonical.com> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'any', NULL, 2, 1, 19, 8, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1820+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (25, 1, '1.0.9a-4ubuntu1', '2005-08-01 14:10:00', 1, NULL, 1, 'alsa-utils (1.0.9a-4ubuntu1) hoary; urgency=low
1821
1822 * Placeholder
1823 LP: #10
1824@@ -2396,22 +2426,22 @@
1825 LP: #7, #8,
1826 #11
1827
1828- -- Sample Person <test@canonical.com> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'all', NULL, 1, 16, 19, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1829-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (26, 1, 'cr.g7-37', '2005-12-22 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 1, 16, 20, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1830-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (27, 1, 'b8p', '2006-02-10 11:19:00', 1, NULL, 1, 'libstdc++ (9.9-1) hoary; urgency=high
1831+ -- Sample Person <test@canonical.com> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'all', NULL, 1, 16, 19, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1832+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (26, 1, 'cr.g7-37', '2005-12-22 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 1, 16, 20, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1833+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (27, 1, 'b8p', '2006-02-10 11:19:00', 1, NULL, 1, 'libstdc++ (9.9-1) hoary; urgency=high
1834
1835 * Placeholder
1836
1837- -- Sample Person <test@canonical.com> Tue, 10 Feb 2006 10:10:08 +0300', NULL, NULL, 'powerpc i386', NULL, 1, 16, 21, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1838-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (28, 1, '2.6.15.3', '2005-12-22 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'any', NULL, 1, 16, 22, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1839-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (29, 1, '0.00', '2005-12-22 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 17, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1840-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (30, 1, '1.0', '2006-09-28 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 20, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1841-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (31, 1, '1.0', '2006-09-28 18:19:01', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 20, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1842-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (32, 1, '1.0', '2006-12-01 13:19:01', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 23, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1843-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (33, 1, '1.0', '2006-12-01 13:19:01', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 24, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1844-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (34, 1, '1.0', '2007-02-15 14:19:01', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 29, 16, 25, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
1845-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (35, 1, '1.0', '2006-04-11 11:19:01', 1, NULL, 1, NULL, NULL, NULL, 'any', NULL, 1, 16, 26, 1, 1, NULL, NULL, '1.0', NULL, 10, NULL, NULL, NULL, NULL, NULL, NULL);
1846-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (36, 243606, '1.0-1', '2007-08-09 21:25:37.832976', 1, NULL, 5, 'commercialpackage (1.0-1) breezy; urgency=low
1847+ -- Sample Person <test@canonical.com> Tue, 10 Feb 2006 10:10:08 +0300', NULL, NULL, 'powerpc i386', NULL, 1, 16, 21, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1848+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (28, 1, '2.6.15.3', '2005-12-22 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'any', NULL, 1, 16, 22, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1849+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (29, 1, '0.00', '2005-12-22 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 17, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1850+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (30, 1, '1.0', '2006-09-28 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 20, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1851+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (31, 1, '1.0', '2006-09-28 18:19:01', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 20, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1852+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (32, 1, '1.0', '2006-12-01 13:19:01', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 23, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1853+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (33, 1, '1.0', '2006-12-01 13:19:01', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 24, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1854+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (34, 1, '1.0', '2007-02-15 14:19:01', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 29, 16, 25, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1855+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (35, 1, '1.0', '2006-04-11 11:19:01', 1, NULL, 1, NULL, NULL, NULL, 'any', NULL, 1, 16, 26, 1, 1, NULL, NULL, '1.0', NULL, 10, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1856+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (36, 243606, '1.0-1', '2007-08-09 21:25:37.832976', 1, NULL, 5, 'commercialpackage (1.0-1) breezy; urgency=low
1857
1858 * Initial version
1859 Address for testing linkification: Foo Bar <foo.bar@canonical.com>
1860@@ -2435,8 +2465,8 @@
1861 iD8DBQFGtzTjWhGlTF8G/HcRAtFsAJ4hHyKhOnsUOQDI+SAk000DmFAnUgCcC84J
1862 3F4bEPeRcnUjCFI/hjR0kxg=
1863 =Tjln
1864-', 7, 243606, 27, 10, 1, 'Julian Edwards <launchpad@julian-edwards.com>', '3.6.2', '1.0', 'commercialpackage', 12, NULL, NULL, NULL, NULL, NULL, NULL);
1865-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (37, 1, '1.0', '2006-04-11 11:19:01', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 1, 16, 26, 1, 1, NULL, NULL, '1.0', NULL, 11, NULL, NULL, NULL, NULL, NULL, NULL);
1866+', 7, 243606, 27, 10, 1, 'Julian Edwards <launchpad@julian-edwards.com>', '3.6.2', '1.0', 'commercialpackage', 12, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1867+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (37, 1, '1.0', '2006-04-11 11:19:01', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 1, 16, 26, 1, 1, NULL, NULL, '1.0', NULL, 11, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1868
1869
1870 ALTER TABLE sourcepackagerelease ENABLE TRIGGER ALL;
1871@@ -2490,21 +2520,21 @@
1872
1873 ALTER TABLE binarypackagerelease DISABLE TRIGGER ALL;
1874
1875-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (6, 6, '1.0', 'foobar is bad', 'foobar should be removed', 6, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2006-12-01 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
1876-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (12, 8, '0.9', 'Mozilla Firefox Web Browser', 'Mozilla Firefox Web Browser is .....', 2, 1, 1, 1, 10, NULL, 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'firefox-gnome-support (= 1.0.7-0ubuntu20), latex-xft-fonts, xprint', 'firefox, mozilla-web-browser', 'gnome-mozilla-browser', 'mozilla-firefox', false, NULL, true, NULL, '2005-10-19 17:50:10.874189', 'pmount, foo', 'pmount, bar', 'pmount, baz', NULL, NULL);
1877-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (15, 13, '0.1-1', 'pmount shortdesc', 'pmount description', 7, 1, 1, 1, 40, NULL, 'at (>= 3.14156), linux-2.6.12, tramp-package', NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
1878-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (16, 14, '2.6.12.20', 'the kernel of boom', 'this kernel is like the crystal method: a temple of boom', 14, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
1879-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (17, 15, '3.14156', 'at the mountains of madness', 'lovecraft long before enunciated that the mountains were not safe, but you did not believe him', 15, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
1880-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (18, 13, '2:1.9-1', 'pmount shortdesc', 'pmount description', 16, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, true, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
1881-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (19, 8, '0.9', 'Mozilla Firefox Web Browser', 'Mozilla Firefox Web Browser is .....', 18, 1, 1, 1, 10, NULL, 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'firefox-gnome-support (= 1.0.7-0ubuntu20), latex-xft-fonts, xprint', 'firefox, mozilla-web-browser', 'gnome-mozilla-browser', 'mozilla-firefox', false, NULL, true, NULL, '2005-10-19 17:50:10.874189', NULL, NULL, NULL, NULL, NULL);
1882-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (20, 13, '0.1-1', 'pmount shortdesc', 'pmount description', 19, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
1883-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (21, 16, '1.0', 'cdrkit is nice', 'cdrkit should be kept', 21, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2006-12-01 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
1884-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (22, 8, '1.0', 'ff from iceweasel', 'iceweasel huh ?', 23, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, true, NULL, '2006-04-11 12:50:10.878712', NULL, NULL, NULL, NULL, NULL);
1885-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (23, 13, '0.1-1', 'pmount shortdesc', 'pmount description', 27, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2007-07-24 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
1886-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (24, 8, '1.0', 'ff from iceweasel', 'iceweasel huh ?', 28, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, true, NULL, '2006-08-10 12:50:10.878712', NULL, NULL, NULL, NULL, NULL);
1887-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (25, 17, '1.0-1', 'Stuff for testing', ' This package is simply used for testing soyuz', 29, 1, 5, 7, 20, '', '', '', '', '', '', '', false, 8, true, NULL, '2007-08-09 21:54:18.456616', NULL, NULL, NULL, NULL, NULL);
1888-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (26, 8, '0.9', 'Mozilla Firefox Web Browser', 'Mozilla Firefox Web Browser is .....', 30, 1, 1, 1, 10, '', '', '', '', '', '', '', false, NULL, true, NULL, '2005-10-19 17:50:10.874189', NULL, NULL, NULL, NULL, NULL);
1889-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (27, 18, '0.9', 'Mozilla Firefox Data', 'Mozilla Firefox Data is .....', 2, 1, 1, 1, 10, '', '', '', '', '', '', '', false, NULL, false, NULL, '2005-10-19 17:50:10.874189', NULL, NULL, NULL, NULL, NULL);
1890+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (6, 6, '1.0', 'foobar is bad', 'foobar should be removed', 6, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2006-12-01 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
1891+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (12, 8, '0.9', 'Mozilla Firefox Web Browser', 'Mozilla Firefox Web Browser is .....', 2, 1, 1, 1, 10, NULL, 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'firefox-gnome-support (= 1.0.7-0ubuntu20), latex-xft-fonts, xprint', 'firefox, mozilla-web-browser', 'gnome-mozilla-browser', 'mozilla-firefox', false, NULL, true, NULL, '2005-10-19 17:50:10.874189', 'pmount, foo', 'pmount, bar', 'pmount, baz', NULL, NULL, NULL);
1892+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (15, 13, '0.1-1', 'pmount shortdesc', 'pmount description', 7, 1, 1, 1, 40, NULL, 'at (>= 3.14156), linux-2.6.12, tramp-package', NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
1893+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (16, 14, '2.6.12.20', 'the kernel of boom', 'this kernel is like the crystal method: a temple of boom', 14, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
1894+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (17, 15, '3.14156', 'at the mountains of madness', 'lovecraft long before enunciated that the mountains were not safe, but you did not believe him', 15, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
1895+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (18, 13, '2:1.9-1', 'pmount shortdesc', 'pmount description', 16, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, true, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
1896+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (19, 8, '0.9', 'Mozilla Firefox Web Browser', 'Mozilla Firefox Web Browser is .....', 18, 1, 1, 1, 10, NULL, 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'firefox-gnome-support (= 1.0.7-0ubuntu20), latex-xft-fonts, xprint', 'firefox, mozilla-web-browser', 'gnome-mozilla-browser', 'mozilla-firefox', false, NULL, true, NULL, '2005-10-19 17:50:10.874189', NULL, NULL, NULL, NULL, NULL, NULL);
1897+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (20, 13, '0.1-1', 'pmount shortdesc', 'pmount description', 19, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
1898+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (21, 16, '1.0', 'cdrkit is nice', 'cdrkit should be kept', 21, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2006-12-01 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
1899+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (22, 8, '1.0', 'ff from iceweasel', 'iceweasel huh ?', 23, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, true, NULL, '2006-04-11 12:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
1900+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (23, 13, '0.1-1', 'pmount shortdesc', 'pmount description', 27, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2007-07-24 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
1901+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (24, 8, '1.0', 'ff from iceweasel', 'iceweasel huh ?', 28, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, true, NULL, '2006-08-10 12:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
1902+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (25, 17, '1.0-1', 'Stuff for testing', ' This package is simply used for testing soyuz', 29, 1, 5, 7, 20, '', '', '', '', '', '', '', false, 8, true, NULL, '2007-08-09 21:54:18.456616', NULL, NULL, NULL, NULL, NULL, NULL);
1903+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (26, 8, '0.9', 'Mozilla Firefox Web Browser', 'Mozilla Firefox Web Browser is .....', 30, 1, 1, 1, 10, '', '', '', '', '', '', '', false, NULL, true, NULL, '2005-10-19 17:50:10.874189', NULL, NULL, NULL, NULL, NULL, NULL);
1904+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (27, 18, '0.9', 'Mozilla Firefox Data', 'Mozilla Firefox Data is .....', 2, 1, 1, 1, 10, '', '', '', '', '', '', '', false, NULL, false, NULL, '2005-10-19 17:50:10.874189', NULL, NULL, NULL, NULL, NULL, NULL);
1905
1906
1907 ALTER TABLE binarypackagerelease ENABLE TRIGGER ALL;
1908@@ -3074,25 +3104,25 @@
1909
1910 ALTER TABLE branchrevision DISABLE TRIGGER ALL;
1911
1912-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (1, 1, 10, 1);
1913-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (2, 1, 11, 2);
1914-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (3, 1, 12, 3);
1915-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (4, 1, 20, 4);
1916-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (5, 2, 20, 5);
1917-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (6, 3, 20, 6);
1918-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (7, 4, 20, 7);
1919-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (8, 5, 20, 8);
1920-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (9, 6, 20, 9);
1921-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (10, 1, 21, 4);
1922-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (11, 2, 21, 5);
1923-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (12, 3, 21, 10);
1924-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (13, 4, 21, 11);
1925-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (14, 5, 21, 8);
1926-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (15, 6, 21, 9);
1927-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (16, NULL, 20, 10);
1928-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (17, NULL, 20, 11);
1929-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (18, NULL, 21, 6);
1930-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (19, NULL, 21, 7);
1931+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 10, 1);
1932+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 11, 2);
1933+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 12, 3);
1934+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 20, 4);
1935+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 21, 4);
1936+INSERT INTO branchrevision (sequence, branch, revision) VALUES (2, 20, 5);
1937+INSERT INTO branchrevision (sequence, branch, revision) VALUES (2, 21, 5);
1938+INSERT INTO branchrevision (sequence, branch, revision) VALUES (3, 20, 6);
1939+INSERT INTO branchrevision (sequence, branch, revision) VALUES (3, 21, 10);
1940+INSERT INTO branchrevision (sequence, branch, revision) VALUES (4, 20, 7);
1941+INSERT INTO branchrevision (sequence, branch, revision) VALUES (4, 21, 11);
1942+INSERT INTO branchrevision (sequence, branch, revision) VALUES (5, 20, 8);
1943+INSERT INTO branchrevision (sequence, branch, revision) VALUES (5, 21, 8);
1944+INSERT INTO branchrevision (sequence, branch, revision) VALUES (6, 20, 9);
1945+INSERT INTO branchrevision (sequence, branch, revision) VALUES (6, 21, 9);
1946+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 20, 10);
1947+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 20, 11);
1948+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 21, 6);
1949+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 21, 7);
1950
1951
1952 ALTER TABLE branchrevision ENABLE TRIGGER ALL;
1953@@ -3315,42 +3345,42 @@
1954
1955 ALTER TABLE bugmessage DISABLE TRIGGER ALL;
1956
1957-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (1, 2, 1, NULL, NULL, true);
1958-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (2, 1, 3, NULL, NULL, true);
1959-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (3, 1, 4, NULL, NULL, true);
1960-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (4, 2, 5, NULL, NULL, true);
1961-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (5, 2, 6, NULL, NULL, true);
1962-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (6, 4, 7, NULL, NULL, true);
1963-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (7, 5, 8, NULL, NULL, true);
1964-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (8, 6, 9, NULL, NULL, true);
1965-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (9, 3, 10, NULL, NULL, true);
1966-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (10, 7, 11, NULL, NULL, true);
1967-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (11, 8, 14, NULL, NULL, true);
1968-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (12, 9, 15, NULL, NULL, true);
1969-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (13, 10, 17, NULL, NULL, true);
1970-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (14, 10, 16, NULL, NULL, true);
1971-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (15, 11, 24, NULL, NULL, true);
1972-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (16, 11, 25, NULL, NULL, true);
1973-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (17, 11, 26, NULL, NULL, true);
1974-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (18, 11, 27, NULL, NULL, true);
1975-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (19, 11, 28, NULL, NULL, true);
1976-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (20, 11, 29, NULL, NULL, true);
1977-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (21, 11, 30, NULL, NULL, true);
1978-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (22, 12, 31, NULL, NULL, true);
1979-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (23, 12, 33, NULL, NULL, true);
1980-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (24, 12, 34, NULL, NULL, true);
1981-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (25, 12, 35, NULL, NULL, true);
1982-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (26, 12, 36, NULL, NULL, true);
1983-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (27, 13, 37, NULL, NULL, true);
1984-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (28, 13, 38, NULL, NULL, true);
1985-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (29, 14, 39, NULL, NULL, true);
1986-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (30, 15, 40, NULL, NULL, true);
1987-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (31, 15, 44, 11, '<4284D7D1.6010208@gmx.de>', true);
1988-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (32, 15, 45, 11, '<20050517185429.GB20786@spring.luon.net>', true);
1989-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (33, 15, 46, 11, '<428A44E9.6090802@gmx.de>', true);
1990-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (34, 15, 47, 11, '<20050517202044.GA23231@spring.luon.net>', true);
1991-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (35, 15, 48, 11, '<20050617140011.GA15638@piware.de>', true);
1992-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (36, 15, 49, 11, '<42BD2E36.9090809@gmx.de>', true);
1993+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (1, 2, 1, NULL, NULL, true, NULL);
1994+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (2, 1, 3, NULL, NULL, true, NULL);
1995+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (3, 1, 4, NULL, NULL, true, NULL);
1996+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (4, 2, 5, NULL, NULL, true, NULL);
1997+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (5, 2, 6, NULL, NULL, true, NULL);
1998+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (6, 4, 7, NULL, NULL, true, NULL);
1999+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (7, 5, 8, NULL, NULL, true, NULL);
2000+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (8, 6, 9, NULL, NULL, true, NULL);
2001+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (9, 3, 10, NULL, NULL, true, NULL);
2002+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (10, 7, 11, NULL, NULL, true, NULL);
2003+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (11, 8, 14, NULL, NULL, true, NULL);
2004+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (12, 9, 15, NULL, NULL, true, NULL);
2005+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (13, 10, 17, NULL, NULL, true, NULL);
2006+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (14, 10, 16, NULL, NULL, true, NULL);
2007+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (15, 11, 24, NULL, NULL, true, NULL);
2008+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (16, 11, 25, NULL, NULL, true, NULL);
2009+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (17, 11, 26, NULL, NULL, true, NULL);
2010+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (18, 11, 27, NULL, NULL, true, NULL);
2011+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (19, 11, 28, NULL, NULL, true, NULL);
2012+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (20, 11, 29, NULL, NULL, true, NULL);
2013+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (21, 11, 30, NULL, NULL, true, NULL);
2014+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (22, 12, 31, NULL, NULL, true, NULL);
2015+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (23, 12, 33, NULL, NULL, true, NULL);
2016+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (24, 12, 34, NULL, NULL, true, NULL);
2017+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (25, 12, 35, NULL, NULL, true, NULL);
2018+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (26, 12, 36, NULL, NULL, true, NULL);
2019+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (27, 13, 37, NULL, NULL, true, NULL);
2020+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (28, 13, 38, NULL, NULL, true, NULL);
2021+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (29, 14, 39, NULL, NULL, true, NULL);
2022+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (30, 15, 40, NULL, NULL, true, NULL);
2023+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (31, 15, 44, 11, '<4284D7D1.6010208@gmx.de>', true, NULL);
2024+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (32, 15, 45, 11, '<20050517185429.GB20786@spring.luon.net>', true, NULL);
2025+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (33, 15, 46, 11, '<428A44E9.6090802@gmx.de>', true, NULL);
2026+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (34, 15, 47, 11, '<20050517202044.GA23231@spring.luon.net>', true, NULL);
2027+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (35, 15, 48, 11, '<20050617140011.GA15638@piware.de>', true, NULL);
2028+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (36, 15, 49, 11, '<42BD2E36.9090809@gmx.de>', true, NULL);
2029
2030
2031 ALTER TABLE bugmessage ENABLE TRIGGER ALL;
2032@@ -3674,6 +3704,45 @@
2033 ALTER TABLE bugsubscription ENABLE TRIGGER ALL;
2034
2035
2036+ALTER TABLE structuralsubscription DISABLE TRIGGER ALL;
2037+
2038+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (1, NULL, NULL, NULL, NULL, 1, NULL, 1, 16, 16, 40, 10, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
2039+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (2, NULL, NULL, NULL, NULL, 1, NULL, 14, 16, 16, 40, 10, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
2040+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (3, 22, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, 40, 10, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
2041+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (4, 16, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, 40, 10, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
2042+
2043+
2044+ALTER TABLE structuralsubscription ENABLE TRIGGER ALL;
2045+
2046+
2047+ALTER TABLE bugsubscriptionfilter DISABLE TRIGGER ALL;
2048+
2049+
2050+
2051+ALTER TABLE bugsubscriptionfilter ENABLE TRIGGER ALL;
2052+
2053+
2054+ALTER TABLE bugsubscriptionfilterimportance DISABLE TRIGGER ALL;
2055+
2056+
2057+
2058+ALTER TABLE bugsubscriptionfilterimportance ENABLE TRIGGER ALL;
2059+
2060+
2061+ALTER TABLE bugsubscriptionfilterstatus DISABLE TRIGGER ALL;
2062+
2063+
2064+
2065+ALTER TABLE bugsubscriptionfilterstatus ENABLE TRIGGER ALL;
2066+
2067+
2068+ALTER TABLE bugsubscriptionfiltertag DISABLE TRIGGER ALL;
2069+
2070+
2071+
2072+ALTER TABLE bugsubscriptionfiltertag ENABLE TRIGGER ALL;
2073+
2074+
2075 ALTER TABLE bugtag DISABLE TRIGGER ALL;
2076
2077 INSERT INTO bugtag (id, bug, tag) VALUES (1, 9, 'crash');
2078@@ -3735,6 +3804,27 @@
2079 ALTER TABLE bugtrackeralias ENABLE TRIGGER ALL;
2080
2081
2082+ALTER TABLE bugtrackercomponentgroup DISABLE TRIGGER ALL;
2083+
2084+
2085+
2086+ALTER TABLE bugtrackercomponentgroup ENABLE TRIGGER ALL;
2087+
2088+
2089+ALTER TABLE distributionsourcepackage DISABLE TRIGGER ALL;
2090+
2091+
2092+
2093+ALTER TABLE distributionsourcepackage ENABLE TRIGGER ALL;
2094+
2095+
2096+ALTER TABLE bugtrackercomponent DISABLE TRIGGER ALL;
2097+
2098+
2099+
2100+ALTER TABLE bugtrackercomponent ENABLE TRIGGER ALL;
2101+
2102+
2103 ALTER TABLE bugtrackerperson DISABLE TRIGGER ALL;
2104
2105
2106@@ -3936,6 +4026,13 @@
2107 ALTER TABLE distributionbounty ENABLE TRIGGER ALL;
2108
2109
2110+ALTER TABLE distributionjob DISABLE TRIGGER ALL;
2111+
2112+
2113+
2114+ALTER TABLE distributionjob ENABLE TRIGGER ALL;
2115+
2116+
2117 ALTER TABLE distributionmirror DISABLE TRIGGER ALL;
2118
2119 INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer, country_dns_mirror) VALUES (1, 1, 'archive-mirror', 'http://localhost:11375/valid-mirror/', NULL, NULL, NULL, NULL, 1, 10, 75, 1, true, true, '2006-10-16 18:31:43.434567', NULL, 30, NULL, NULL, false);
2120@@ -3953,13 +4050,6 @@
2121 ALTER TABLE distributionmirror ENABLE TRIGGER ALL;
2122
2123
2124-ALTER TABLE distributionsourcepackage DISABLE TRIGGER ALL;
2125-
2126-
2127-
2128-ALTER TABLE distributionsourcepackage ENABLE TRIGGER ALL;
2129-
2130-
2131 ALTER TABLE distributionsourcepackagecache DISABLE TRIGGER ALL;
2132
2133 INSERT INTO distributionsourcepackagecache (id, distribution, sourcepackagename, name, binpkgnames, binpkgsummaries, binpkgdescriptions, fti, changelog, archive) VALUES (1, 3, 19, 'alsa-utils', '', '', '', NULL, NULL, 1);
2134@@ -4000,6 +4090,27 @@
2135 ALTER TABLE distrocomponentuploader ENABLE TRIGGER ALL;
2136
2137
2138+ALTER TABLE packagediff DISABLE TRIGGER ALL;
2139+
2140+
2141+
2142+ALTER TABLE packagediff ENABLE TRIGGER ALL;
2143+
2144+
2145+ALTER TABLE distroseriesdifference DISABLE TRIGGER ALL;
2146+
2147+
2148+
2149+ALTER TABLE distroseriesdifference ENABLE TRIGGER ALL;
2150+
2151+
2152+ALTER TABLE distroseriesdifferencemessage DISABLE TRIGGER ALL;
2153+
2154+
2155+
2156+ALTER TABLE distroseriesdifferencemessage ENABLE TRIGGER ALL;
2157+
2158+
2159 ALTER TABLE distroserieslanguage DISABLE TRIGGER ALL;
2160
2161 INSERT INTO distroserieslanguage (id, distroseries, language, currentcount, updatescount, rosettacount, contributorcount, dateupdated, unreviewed_count) VALUES (1, 3, 68, 62, 0, 0, 1, '2007-01-15 17:58:40.839938', 0);
2162@@ -4489,6 +4600,13 @@
2163 ALTER TABLE hwtestanswerdevice ENABLE TRIGGER ALL;
2164
2165
2166+ALTER TABLE incrementaldiff DISABLE TRIGGER ALL;
2167+
2168+
2169+
2170+ALTER TABLE incrementaldiff ENABLE TRIGGER ALL;
2171+
2172+
2173 ALTER TABLE ircid DISABLE TRIGGER ALL;
2174
2175 INSERT INTO ircid (id, person, network, nickname) VALUES (1, 1, 'irc.freenode.net', 'mark');
2176@@ -5986,13 +6104,6 @@
2177 ALTER TABLE packagecopyrequest ENABLE TRIGGER ALL;
2178
2179
2180-ALTER TABLE packagediff DISABLE TRIGGER ALL;
2181-
2182-
2183-
2184-ALTER TABLE packagediff ENABLE TRIGGER ALL;
2185-
2186-
2187 ALTER TABLE packageselection DISABLE TRIGGER ALL;
2188
2189
2190@@ -10462,17 +10573,6 @@
2191 ALTER TABLE standardshipitrequest ENABLE TRIGGER ALL;
2192
2193
2194-ALTER TABLE structuralsubscription DISABLE TRIGGER ALL;
2195-
2196-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (1, NULL, NULL, NULL, NULL, 1, NULL, 1, 16, 16, 40, 10, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
2197-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (2, NULL, NULL, NULL, NULL, 1, NULL, 14, 16, 16, 40, 10, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
2198-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (3, 22, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, 40, 10, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
2199-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (4, 16, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, 40, 10, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
2200-
2201-
2202-ALTER TABLE structuralsubscription ENABLE TRIGGER ALL;
2203-
2204-
2205 ALTER TABLE suggestivepotemplate DISABLE TRIGGER ALL;
2206
2207
2208@@ -11081,6 +11181,13 @@
2209 ALTER TABLE translationtemplateitem ENABLE TRIGGER ALL;
2210
2211
2212+ALTER TABLE translationtemplatesbuild DISABLE TRIGGER ALL;
2213+
2214+
2215+
2216+ALTER TABLE translationtemplatesbuild ENABLE TRIGGER ALL;
2217+
2218+
2219 ALTER TABLE translator DISABLE TRIGGER ALL;
2220
2221 INSERT INTO translator (id, translationgroup, language, translator, datecreated, style_guide_url) VALUES (1, 1, 387, 53, '2005-07-13 13:14:19.748396', NULL);
2222
2223=== modified file 'database/schema/comments.sql'
2224--- database/schema/comments.sql 2010-09-03 16:43:11 +0000
2225+++ database/schema/comments.sql 2010-10-04 22:08:24 +0000
2226@@ -346,6 +346,21 @@
2227 COMMENT ON COLUMN BugTrackerPerson.name IS 'The (within the bug tracker) unique username in the external bug tracker.';
2228 COMMENT ON COLUMN BugTrackerPerson.person IS 'The Person record in Launchpad this user corresponds to.';
2229
2230+-- BugTrackerComponent
2231+
2232+COMMENT ON TABLE BugTrackerComponent IS 'A software component in a remote bug tracker, which can be linked to the corresponding source package in a distribution using this table.';
2233+COMMENT ON COLUMN BugTrackerComponent.name IS 'The name of the component as registered in the remote bug tracker.';
2234+COMMENT ON COLUMN BugTrackerComponent.is_visible IS 'Whether to display or hide the item in the Launchpad user interface.';
2235+COMMENT ON COLUMN BugTrackerComponent.is_custom IS 'Whether the item was added by a user in Launchpad or is kept in sync with the remote bug tracker.';
2236+COMMENT ON COLUMN BugTrackerComponent.component_group IS 'The product or other higher level category used by the remote bug tracker to group projects, if any.';
2237+COMMENT ON COLUMN BugTrackerComponent.distro_source_package IS 'A link to the source package in a distribution that corresponds to this component. This can be undefined if no link has been established yet.';
2238+
2239+-- BugTrackerComponentGroup
2240+
2241+COMMENT ON TABLE BugTrackerComponentGroup IS 'A collection of components as modeled in a remote bug tracker, often referred to as a product. Some bug trackers do not categorize software components this way, so they will have a single default component group that all components belong to.';
2242+COMMENT ON COLUMN BugTrackerComponentGroup.name IS 'The product or category name used in the remote bug tracker for grouping components.';
2243+COMMENT ON COLUMN BugTrackerComponentGroup.bug_tracker IS 'The external bug tracker this component group belongs to.';
2244+
2245 -- BugCve
2246
2247 COMMENT ON TABLE BugCve IS 'A table that records the link between a given malone bug number, and a CVE entry.';
2248@@ -532,9 +547,12 @@
2249 COMMENT ON TABLE DistroSeriesDifference IS 'A difference of versions for a package in a derived distroseries and its parent distroseries.';
2250 COMMENT ON COLUMN DistroSeriesDifference.derived_series IS 'The derived distroseries with the difference from its parent.';
2251 COMMENT ON COLUMN DistroSeriesDifference.source_package_name IS 'The name of the source package which is different in the two series.';
2252-COMMENT ON COLUMN DistroSeriesDifference.package_diff IS 'The most recent package diff that was created for this difference.';
2253+COMMENT ON COLUMN DistroSeriesDifference.package_diff IS 'The most recent package diff that was created for the base version to derived version.';
2254+COMMENT ON COLUMN DistroSeriesDifference.parent_package_diff IS 'The most recent package diff that was created for the base version to the parent version.';
2255 COMMENT ON COLUMN DistroSeriesDifference.status IS 'A distroseries difference can be needing attention, ignored or resolved.';
2256 COMMENT ON COLUMN DistroSeriesDifference.difference_type IS 'The type of difference that this record represents - a package unique to the derived series, or missing, or in both.';
2257+COMMENT ON COLUMN DistroSeriesDifference.source_version IS 'The version of the package in the derived series.';
2258+COMMENT ON COLUMN DistroSeriesDifference.parent_source_version IS 'The version of the package in the parent series.';
2259
2260 -- DistroSeriesDifferenceMessage
2261 COMMENT ON TABLE DistroSeriesDifferenceMessage IS 'A message/comment on a distro series difference.';
2262@@ -839,6 +857,11 @@
2263 COMMENT ON COLUMN TranslationRelicensingAgreement.allow_relicensing IS 'Does this person want their translations relicensed under BSD.';
2264 COMMENT ON COLUMN TranslationRelicensingAgreement.date_decided IS 'Date when the last change of opinion was registered.';
2265
2266+-- TranslationTemplatesBuild
2267+COMMENT ON TABLE TranslationTemplatesBuild IS 'Build-farm record of a translation templates build.';
2268+COMMENT ON COLUMN TranslationTemplatesBuild.build_farm_job IS 'Associated BuildFarmJob.';
2269+COMMENT ON COLUMN TranslationTemplatesBuild.branch IS 'Branch to build templates out of.';
2270+
2271 -- RevisionAuthor
2272 COMMENT ON TABLE RevisionAuthor IS 'All distinct authors for revisions.';
2273 COMMENT ON COLUMN RevisionAuthor.name IS 'The exact text extracted from the branch revision.';
2274@@ -1171,6 +1194,7 @@
2275 COMMENT ON COLUMN DistroArchSeries.package_count IS 'A cache of the number of binary packages published in this distro arch release. The count only includes packages published in the release pocket.';
2276 COMMENT ON COLUMN DistroArchSeries.supports_virtualized IS 'Whether or not
2277 virtualized build support should be provided by this specific distroarchseries';
2278+COMMENT ON COLUMN DistroArchSeries.enabled IS 'Whether to allow build creation and publishing for this DistroArchSeries.';
2279
2280 -- LauncpadDatabaseRevision
2281 COMMENT ON TABLE LaunchpadDatabaseRevision IS 'This table contains a list of the database patches that have been successfully applied to this database.';
2282@@ -1288,11 +1312,14 @@
2283
2284 COMMENT ON TABLE ProjectBounty IS 'This table records a simple link between a bounty and a project. This bounty will be listed on the project web page, and the project will be mentioned on the bounty web page.';
2285
2286--- Messaging subsytem
2287+-- BugMessages
2288 COMMENT ON TABLE BugMessage IS 'This table maps a message to a bug. In other words, it shows that a particular message is associated with a particular bug.';
2289 COMMENT ON COLUMN BugMessage.bugwatch IS 'The external bug this bug comment was imported from.';
2290 COMMENT ON COLUMN BugMessage.remote_comment_id IS 'The id this bug comment has in the external bug tracker, if it is an imported comment. If it is NULL while having a bugwatch set, this comment was added in Launchpad and needs to be pushed to the external bug tracker.';
2291 COMMENT ON COLUMN BugMessage.visible IS 'If false, the bug comment is hidden and should not be shown in any UI.';
2292+COMMENT ON COLUMN BugMessage.index IS 'The index (used in urls) of the message in a particular bug.';
2293+
2294+-- Messaging subsytem
2295 COMMENT ON TABLE Message IS 'This table stores a single RFC822-style message. Messages can be threaded (using the parent field). These messages can then be referenced from elsewhere in the system, such as the BugMessage table, integrating messageboard facilities with the rest of The Launchpad.';
2296 COMMENT ON COLUMN Message.parent IS 'A "parent message". This allows for some level of threading in Messages.';
2297 COMMENT ON COLUMN Message.subject IS 'The title text of the message, or the subject if it was an email.';
2298@@ -1932,6 +1959,14 @@
2299 COMMENT ON COLUMN Continent.code IS 'A two-letter code for a continent.';
2300 COMMENT ON COLUMN Continent.name IS 'The name of the continent.';
2301
2302+-- DistributionJob
2303+
2304+COMMENT ON TABLE DistributionJob IS 'Contains references to jobs to be run on distributions.';
2305+COMMENT ON COLUMN DistributionJob.distribution IS 'The distribution to be acted on.';
2306+COMMENT ON COLUMN DistributionJob.distroseries IS 'The distroseries to be acted on.';
2307+COMMENT ON COLUMN DistributionJob.job_type IS 'The type of job';
2308+COMMENT ON COLUMN DistributionJob.json_data IS 'A JSON struct containing data for the job.';
2309+
2310 -- DistributionMirror
2311 COMMENT ON TABLE DistributionMirror IS 'A mirror of a given distribution.';
2312 COMMENT ON COLUMN DistributionMirror.distribution IS 'The distribution to which the mirror refers to.';
2313@@ -2303,6 +2338,14 @@
2314 COMMENT ON COLUMN HWDMIValue.value IS 'The value';
2315 COMMENT ON COLUMN HWDMIValue.handle IS 'The handle to which this key/value pair belongs.';
2316
2317+-- IncrementalDiff
2318+COMMENT ON TABLE IncrementalDiff IS 'Incremental diffs for merge proposals.';
2319+COMMENT ON COLUMN IncrementalDiff.diff IS 'The contents of the diff.';
2320+COMMENT ON COLUMN IncrementalDiff.branch_merge_proposal IS 'The merge proposal the diff is for.';
2321+COMMENT ON COLUMN IncrementalDiff.old_revision IS 'The revision the diff is from.';
2322+COMMENT ON COLUMN IncrementalDiff.new_revision IS 'The revision the diff is to.';
2323+
2324+
2325 -- Job
2326
2327 COMMENT ON TABLE Job IS 'Common info about a job.';
2328
2329=== modified file 'database/schema/launchpad_session.sql'
2330--- database/schema/launchpad_session.sql 2010-09-06 09:48:41 +0000
2331+++ database/schema/launchpad_session.sql 2010-10-04 22:08:24 +0000
2332@@ -17,14 +17,13 @@
2333 CREATE TABLE TimeLimitedToken (
2334 path text NOT NULL,
2335 token text NOT NULL,
2336- created timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
2337- constraint timelimitedtoken_pky primary key (path, token)
2338+ created timestamp without time zone
2339+ NOT NULL DEFAULT (CURRENT_TIMESTAMP AT TIME ZONE 'UTC'),
2340+ constraint timelimitedtoken_pkey primary key (path, token)
2341 ) WITHOUT OIDS;
2342 COMMENT ON TABLE TimeLimitedToken IS 'stores tokens for granting access to a single path in the librarian for a short while. The garbo takes care of cleanups, and we should only have a few thousand at a time. Tokens are handed out just-in-time on the appserver, when a client attempts to dereference a private thing which we do not want to deliver in-line. OAuth tokens cannot be used for the launchpadlibrarian content because they would then be attackable. See lib.canonical.database.librarian for the python class.';
2343 -- Give the garbo an efficient selection to cleanup
2344 CREATE INDEX timelimitedtoken_created ON TimeLimitedToken(created);
2345--- Give the librarian an efficient lookup
2346-CREATE INDEX timelimitedtoken_path_token ON TimeLimitedToken(path, token);
2347
2348 -- Let the session user access file access tokens.
2349 GRANT SELECT, INSERT, UPDATE, DELETE ON TimeLimitedToken TO session;
2350
2351=== added file 'database/schema/patch-2208-08-1.sql'
2352--- database/schema/patch-2208-08-1.sql 1970-01-01 00:00:00 +0000
2353+++ database/schema/patch-2208-08-1.sql 2010-10-04 22:08:24 +0000
2354@@ -0,0 +1,9 @@
2355+-- Copyright 2010 Canonical Ltd. This software is licensed under the
2356+-- GNU Affero General Public License version 3 (see the file LICENSE).
2357+
2358+SET client_min_messages=ERROR;
2359+
2360+ALTER TABLE distroarchseries
2361+ ADD COLUMN enabled bool NOT NULL DEFAULT TRUE;
2362+
2363+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 08, 1);
2364
2365=== added file 'database/schema/patch-2208-08-2.sql'
2366--- database/schema/patch-2208-08-2.sql 1970-01-01 00:00:00 +0000
2367+++ database/schema/patch-2208-08-2.sql 2010-10-04 22:08:24 +0000
2368@@ -0,0 +1,8 @@
2369+SET client_min_messages=ERROR;
2370+
2371+-- Permit bug searches ordered by 'importance' - the default - to serve from
2372+-- index rather than doing the full search and sorting.
2373+
2374+CREATE INDEX bugtask_importance_idx ON BugTask (importance, id desc nulls first);
2375+
2376+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 8, 2);
2377
2378=== added file 'database/schema/patch-2208-08-3.sql'
2379--- database/schema/patch-2208-08-3.sql 1970-01-01 00:00:00 +0000
2380+++ database/schema/patch-2208-08-3.sql 2010-10-04 22:08:24 +0000
2381@@ -0,0 +1,8 @@
2382+SET client_min_messages=ERROR;
2383+
2384+-- Permit bug searches ordered by 'date_closed desc, id' to serve from
2385+-- index rather than doing the full search and sorting.
2386+
2387+CREATE INDEX bugtask__date_closed__id__idx2 ON BugTask (date_closed, id desc nulls first);
2388+
2389+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 8, 3);
2390
2391=== added file 'database/schema/patch-2208-09-0.sql'
2392--- database/schema/patch-2208-09-0.sql 1970-01-01 00:00:00 +0000
2393+++ database/schema/patch-2208-09-0.sql 2010-10-04 22:08:24 +0000
2394@@ -0,0 +1,38 @@
2395+-- Copyright 2010 Canonical Ltd. This software is licensed under the
2396+-- GNU Affero General Public License version 3 (see the file LICENSE).
2397+
2398+SET client_min_messages=ERROR;
2399+
2400+CREATE TABLE BugTrackerComponentGroup (
2401+ id serial PRIMARY KEY,
2402+ name text NOT NULL,
2403+ bug_tracker integer NOT NULL REFERENCES BugTracker,
2404+
2405+ CONSTRAINT valid_name CHECK (valid_name(name))
2406+);
2407+
2408+ALTER TABLE BugTrackerComponentGroup
2409+ ADD CONSTRAINT bugtrackercomponentgroup__bug_tracker__name__key
2410+ UNIQUE (bug_tracker, name);
2411+
2412+
2413+CREATE TABLE BugTrackerComponent (
2414+ id serial PRIMARY KEY,
2415+ name text NOT NULL,
2416+ is_visible boolean NOT NULL DEFAULT True,
2417+ is_custom boolean NOT NULL DEFAULT True,
2418+ component_group integer NOT NULL REFERENCES BugTrackerComponentGroup,
2419+ distro_source_package integer REFERENCES DistributionSourcePackage,
2420+
2421+ CONSTRAINT valid_name CHECK (valid_name(name))
2422+);
2423+
2424+ALTER TABLE BugTrackerComponent
2425+ ADD CONSTRAINT bugtrackercomponent__component_group__name__key
2426+ UNIQUE (component_group, name);
2427+
2428+ALTER TABLE BugTrackerComponent
2429+ ADD CONSTRAINT bugtrackercomponent__distro_source_package__key
2430+ UNIQUE (distro_source_package);
2431+
2432+INSERT INTO LaunchpadDatabaseRevision VALUES(2208, 09, 0);
2433
2434=== added file 'database/schema/patch-2208-10-0.sql'
2435--- database/schema/patch-2208-10-0.sql 1970-01-01 00:00:00 +0000
2436+++ database/schema/patch-2208-10-0.sql 2010-10-04 22:08:24 +0000
2437@@ -0,0 +1,25 @@
2438+-- Copyright 2009 Canonical Ltd. This software is licensed under the
2439+-- GNU Affero General Public License version 3 (see the file LICENSE).
2440+
2441+SET client_min_messages=ERROR;
2442+
2443+-- The `InitialiseDistroSeriesJob` table captures the data required for an ifp job.
2444+
2445+CREATE TABLE DistributionJob (
2446+ id serial PRIMARY KEY,
2447+ -- FK to the `Job` record with the "generic" data about this archive
2448+ -- job.
2449+ job integer NOT NULL CONSTRAINT distributionjob__job__fk REFERENCES job,
2450+ -- FK to the associated `Distribution` record.
2451+ distribution integer NOT NULL REFERENCES Distribution,
2452+ distroseries integer REFERENCES DistroSeries,
2453+ -- The particular type of foo job
2454+ job_type integer NOT NULL,
2455+ -- JSON data for use by the job
2456+ json_data text
2457+);
2458+
2459+ALTER TABLE DistributionJob ADD CONSTRAINT distributionjob__job__key UNIQUE (job);
2460+CREATE UNIQUE INDEX distribution_job__initialise_series__distroseries ON DistributionJob (distroseries) WHERE job_type = 1;
2461+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 10, 0);
2462+
2463
2464=== added file 'database/schema/patch-2208-11-0.sql'
2465--- database/schema/patch-2208-11-0.sql 1970-01-01 00:00:00 +0000
2466+++ database/schema/patch-2208-11-0.sql 2010-10-04 22:08:24 +0000
2467@@ -0,0 +1,30 @@
2468+-- Copyright 2010 Canonical Ltd. This software is licensed under the
2469+-- GNU Affero General Public License version 3 (see the file LICENSE).
2470+
2471+SET client_min_messages=ERROR;
2472+
2473+-- This constraint is useless given the current PK
2474+ALTER TABLE BranchRevision
2475+DROP CONSTRAINT revisionnumber_branch_id_unique;
2476+
2477+-- This constraint is no longer needed as it will be the new PK
2478+ALTER TABLE BranchRevision
2479+DROP CONSTRAINT revision__branch__revision__key ;
2480+
2481+-- Kill the old PK
2482+ALTER TABLE BranchRevision
2483+DROP CONSTRAINT revisionnumber_pkey ;
2484+
2485+-- Create the new PK
2486+ALTER TABLE BranchRevision
2487+ADD CONSTRAINT revisionnumber_pkey PRIMARY KEY (branch, revision);
2488+
2489+-- What was this used for? Not used now.
2490+DROP VIEW RevisionNumber;
2491+
2492+-- Kill the old id.
2493+ALTER TABLE BranchRevision
2494+DROP COLUMN id;
2495+
2496+
2497+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 11, 0);
2498
2499=== added file 'database/schema/patch-2208-12-0.sql'
2500--- database/schema/patch-2208-12-0.sql 1970-01-01 00:00:00 +0000
2501+++ database/schema/patch-2208-12-0.sql 2010-10-04 22:08:24 +0000
2502@@ -0,0 +1,17 @@
2503+SET client_min_messages=ERROR;
2504+
2505+-- Allow for package diffs against both derived and parent versions.
2506+ALTER TABLE DistroSeriesDifference ADD COLUMN parent_package_diff integer CONSTRAINT distroseriesdifference__parent_package_diff__fk REFERENCES packagediff;
2507+CREATE INDEX distroseriesdifference__parent_package_diff__idx ON distroseriesdifference(parent_package_diff);
2508+
2509+-- Add columns for source_version and parent_source_version
2510+ALTER TABLE DistroSeriesDifference ADD COLUMN source_version text;
2511+ALTER TABLE DistroSeriesDifference ADD CONSTRAINT valid_source_version CHECK(valid_debian_version(source_version));
2512+ALTER TABLE DistroSeriesDifference ADD COLUMN parent_source_version text;
2513+ALTER TABLE DistroSeriesDifference ADD CONSTRAINT valid_parent_source_version CHECK(valid_debian_version(parent_source_version));
2514+
2515+-- Add a unique constraint/index for the source_package_name/derived series combo and drop the previous index.
2516+ALTER TABLE DistroSeriesDifference ADD CONSTRAINT distroseriesdifference__derived_series__source_package_name__key UNIQUE (derived_series, source_package_name);
2517+DROP INDEX distroseriesdifference__derived_series__idx;
2518+
2519+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 12, 0);
2520
2521=== added file 'database/schema/patch-2208-13-0.sql'
2522--- database/schema/patch-2208-13-0.sql 1970-01-01 00:00:00 +0000
2523+++ database/schema/patch-2208-13-0.sql 2010-10-04 22:08:24 +0000
2524@@ -0,0 +1,17 @@
2525+-- Copyright 2010 Canonical Ltd. This software is licensed under the
2526+-- GNU Affero General Public License version 3 (see the file LICENSE).
2527+
2528+SET client_min_messages=ERROR;
2529+
2530+CREATE TABLE TranslationTemplatesBuild (
2531+ id SERIAL PRIMARY KEY,
2532+ build_farm_job integer NOT NULL REFERENCES BuildFarmJob(id),
2533+ branch integer NOT NULL REFERENCES Branch(id));
2534+
2535+CREATE INDEX translationtemplatesbuild__build_farm_job__idx ON
2536+ TranslationTemplatesBuild(build_farm_job);
2537+
2538+CREATE INDEX translationtemplatesbuild__branch__idx ON
2539+ TranslationTemplatesBuild(branch);
2540+
2541+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 13, 0);
2542
2543=== added file 'database/schema/patch-2208-14-0.sql'
2544--- database/schema/patch-2208-14-0.sql 1970-01-01 00:00:00 +0000
2545+++ database/schema/patch-2208-14-0.sql 2010-10-04 22:08:24 +0000
2546@@ -0,0 +1,9 @@
2547+SET client_min_messages=ERROR;
2548+
2549+-- Store the row index of bug messages so we don't have to calculate it all the time.
2550+ALTER TABLE BugMessage ADD COLUMN index integer;
2551+
2552+-- BugMessage.indexes must be unique per bug.
2553+ALTER TABLE BugMessage ADD CONSTRAINT bugmessage__bug__index__key UNIQUE (bug, index);
2554+
2555+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 14, 0);
2556
2557=== added file 'database/schema/patch-2208-15-0.sql'
2558--- database/schema/patch-2208-15-0.sql 1970-01-01 00:00:00 +0000
2559+++ database/schema/patch-2208-15-0.sql 2010-10-04 22:08:24 +0000
2560@@ -0,0 +1,7 @@
2561+SET client_min_messages=ERROR;
2562+
2563+-- Delete index obsoleted by bugtask__date_closed__id__idx2
2564+
2565+DROP INDEX bugtask__date_closed__id__idx;
2566+
2567+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 15, 0);
2568
2569=== added file 'database/schema/patch-2208-16-0.sql'
2570--- database/schema/patch-2208-16-0.sql 1970-01-01 00:00:00 +0000
2571+++ database/schema/patch-2208-16-0.sql 2010-10-04 22:08:24 +0000
2572@@ -0,0 +1,8 @@
2573+-- Copyright 2010 Canonical Ltd. This software is licensed under the
2574+-- GNU Affero General Public License version 3 (see the file LICENSE).
2575+SET client_min_messages=ERROR;
2576+
2577+ALTER TABLE SourcePackageRecipeBuildJob
2578+ ALTER COLUMN sourcepackage_recipe_build SET NOT NULL;
2579+
2580+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 16, 0);
2581
2582=== added file 'database/schema/patch-2208-17-0.sql'
2583--- database/schema/patch-2208-17-0.sql 1970-01-01 00:00:00 +0000
2584+++ database/schema/patch-2208-17-0.sql 2010-10-04 22:08:24 +0000
2585@@ -0,0 +1,18 @@
2586+-- Copyright 2010 Canonical Ltd. This software is licensed under the
2587+-- GNU Affero General Public License version 3 (see the file LICENSE).
2588+
2589+SET client_min_messages=ERROR;
2590+
2591+CREATE TABLE IncrementalDiff(
2592+ id serial PRIMARY KEY,
2593+ diff integer NOT NULL CONSTRAINT diff_fk REFERENCES Diff ON DELETE CASCADE,
2594+ branch_merge_proposal integer NOT NULL CONSTRAINT branch_merge_proposal_fk REFERENCES BranchMergeProposal ON DELETE CASCADE,
2595+ old_revision integer NOT NULL CONSTRAINT old_revision_fk REFERENCES Revision ON DELETE CASCADE,
2596+ new_revision integer NOT NULL CONSTRAINT new_revision_fk REFERENCES Revision ON DELETE CASCADE);
2597+
2598+CREATE INDEX incrementaldiff__diff__idx ON IncrementalDiff(diff);
2599+CREATE INDEX incrementaldiff__branch_merge_proposal__idx ON IncrementalDiff(branch_merge_proposal);
2600+CREATE INDEX incrementaldiff__old_revision__idx ON IncrementalDiff(old_revision);
2601+CREATE INDEX incrementaldiff__new_revision__idx ON IncrementalDiff(new_revision);
2602+
2603+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 17, 0);
2604
2605=== modified file 'database/schema/security.cfg'
2606--- database/schema/security.cfg 2010-09-21 23:09:59 +0000
2607+++ database/schema/security.cfg 2010-10-04 22:08:24 +0000
2608@@ -121,6 +121,8 @@
2609 public.bugnotificationrecipientarchive = SELECT, UPDATE
2610 public.bugtag = SELECT, INSERT, DELETE
2611 public.bugtrackerperson = SELECT, UPDATE
2612+public.bugtrackercomponent = SELECT, INSERT, UPDATE
2613+public.bugtrackercomponentgroup = SELECT, INSERT, UPDATE
2614 public.bugwatchactivity = SELECT, INSERT, UPDATE
2615 public.codeimport = SELECT, INSERT, UPDATE, DELETE
2616 public.codeimportevent = SELECT, INSERT, UPDATE
2617@@ -138,6 +140,7 @@
2618 public.databasereplicationlag = SELECT
2619 public.diff = SELECT, INSERT, UPDATE
2620 public.distributionbounty = SELECT, INSERT, UPDATE
2621+public.distributionjob = SELECT, INSERT
2622 public.distributionmirror = SELECT, INSERT, UPDATE, DELETE
2623 public.distributionsourcepackage = SELECT, INSERT, UPDATE, DELETE
2624 public.distributionsourcepackagecache = SELECT
2625@@ -169,6 +172,7 @@
2626 public.hwtest = SELECT
2627 public.hwvendorid = SELECT
2628 public.hwvendorname = SELECT
2629+public.incrementaldiff = SELECT, INSERT, UPDATE, DELETE
2630 public.job = SELECT, INSERT, UPDATE, DELETE
2631 public.karmacache = SELECT
2632 public.karmacategory = SELECT
2633@@ -237,7 +241,6 @@
2634 public.revision = SELECT, INSERT, UPDATE
2635 public.revisionauthor = SELECT, INSERT, UPDATE
2636 public.revisioncache = SELECT, INSERT, UPDATE, DELETE
2637-public.revisionnumber = SELECT, INSERT
2638 public.revisionparent = SELECT, INSERT
2639 public.scriptactivity = SELECT
2640 public.shipitreport = SELECT, INSERT
2641@@ -276,6 +279,7 @@
2642 public.translationgroup = SELECT, INSERT, UPDATE
2643 public.translationimportqueueentry = SELECT, INSERT, UPDATE, DELETE
2644 public.translationmessage = SELECT, INSERT, UPDATE
2645+public.translationtemplatesbuild = SELECT, INSERT, UPDATE, DELETE
2646 public.translator = SELECT, INSERT, UPDATE, DELETE
2647 public.usertouseremail = SELECT, UPDATE
2648 public.validpersoncache = SELECT
2649@@ -538,8 +542,14 @@
2650 public.bugnotification = SELECT, INSERT
2651 public.bugnotificationrecipient = SELECT, INSERT
2652 public.bugsubscription = SELECT
2653+public.bugsubscriptionfilter = SELECT
2654+public.bugsubscriptionfilterstatus = SELECT
2655+public.bugsubscriptionfilterimportance = SELECT
2656+public.bugsubscriptionfiltertag = SELECT
2657 public.bugtask = SELECT, INSERT, UPDATE
2658 public.bugtracker = SELECT, INSERT
2659+public.bugtrackercomponent = SELECT, INSERT, UPDATE, DELETE
2660+public.bugtrackercomponentgroup = SELECT, INSERT, UPDATE, DELETE
2661 public.bugtrackeralias = SELECT
2662 public.bugtrackerperson = SELECT, INSERT
2663 public.bugwatch = SELECT, INSERT, UPDATE
2664@@ -595,7 +605,9 @@
2665 public.distribution = SELECT
2666 public.distributionsourcepackage = SELECT, UPDATE
2667 public.emailaddress = SELECT
2668+public.incrementaldiff = SELECT
2669 public.job = SELECT, INSERT, UPDATE, DELETE
2670+public.translationtemplatesbuild = SELECT, INSERT
2671 # Karma
2672 public.karma = SELECT, INSERT
2673 public.karmaaction = SELECT
2674@@ -617,6 +629,10 @@
2675 public.bugactivity = SELECT, INSERT
2676 public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE
2677 public.bugsubscription = SELECT
2678+public.bugsubscriptionfilter = SELECT
2679+public.bugsubscriptionfilterstatus = SELECT
2680+public.bugsubscriptionfilterimportance = SELECT
2681+public.bugsubscriptionfiltertag = SELECT
2682 public.bugnotification = SELECT, INSERT
2683 public.bugnotificationrecipient = SELECT, INSERT
2684 public.structuralsubscription = SELECT
2685@@ -805,6 +821,10 @@
2686 public.bugactivity = SELECT, INSERT
2687 public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE
2688 public.bugsubscription = SELECT
2689+public.bugsubscriptionfilter = SELECT
2690+public.bugsubscriptionfilterstatus = SELECT
2691+public.bugsubscriptionfilterimportance = SELECT
2692+public.bugsubscriptionfiltertag = SELECT
2693 public.bugnotification = SELECT, INSERT
2694 public.bugnotificationrecipient = SELECT, INSERT
2695 public.bugnomination = SELECT
2696@@ -889,6 +909,7 @@
2697 public.flatpackagesetinclusion = SELECT
2698 public.teamparticipation = SELECT
2699 public.translationimportqueueentry = SELECT, INSERT, UPDATE
2700+public.translationtemplatesbuild = SELECT, INSERT
2701
2702 [ppa-apache-log-parser]
2703 type=user
2704@@ -929,6 +950,10 @@
2705 public.bugpackageinfestation = SELECT, INSERT, UPDATE
2706 public.bugproductinfestation = SELECT, INSERT, UPDATE
2707 public.bugsubscription = SELECT, INSERT, UPDATE, DELETE
2708+public.bugsubscriptionfilter = SELECT, INSERT, UPDATE, DELETE
2709+public.bugsubscriptionfilterstatus = SELECT, INSERT, UPDATE, DELETE
2710+public.bugsubscriptionfilterimportance = SELECT, INSERT, UPDATE, DELETE
2711+public.bugsubscriptionfiltertag = SELECT, INSERT, UPDATE, DELETE
2712 public.bugtask = SELECT, INSERT, UPDATE, DELETE
2713 public.bugtracker = SELECT, INSERT, UPDATE, DELETE
2714 public.bugtrackeralias = SELECT, INSERT, UPDATE, DELETE
2715@@ -1157,6 +1182,10 @@
2716 public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE
2717 public.bugjob = SELECT, INSERT
2718 public.bugsubscription = SELECT
2719+public.bugsubscriptionfilter = SELECT
2720+public.bugsubscriptionfilterstatus = SELECT
2721+public.bugsubscriptionfilterimportance = SELECT
2722+public.bugsubscriptionfiltertag = SELECT
2723 public.bugnotification = SELECT, INSERT
2724 public.bugnotificationrecipient = SELECT, INSERT
2725 public.bugnomination = SELECT
2726@@ -1259,6 +1288,10 @@
2727 public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE
2728 public.bugjob = SELECT, INSERT
2729 public.bugsubscription = SELECT
2730+public.bugsubscriptionfilter = SELECT
2731+public.bugsubscriptionfilterstatus = SELECT
2732+public.bugsubscriptionfilterimportance = SELECT
2733+public.bugsubscriptionfiltertag = SELECT
2734 public.bugnotification = SELECT, INSERT
2735 public.bugnotificationrecipient = SELECT, INSERT
2736 public.bugnomination = SELECT
2737@@ -1326,6 +1359,10 @@
2738 public.bugnotification = SELECT, INSERT, UPDATE
2739 public.bugnotificationrecipient = SELECT, INSERT, UPDATE
2740 public.bugsubscription = SELECT, INSERT
2741+public.bugsubscriptionfilter = SELECT, INSERT
2742+public.bugsubscriptionfilterstatus = SELECT, INSERT
2743+public.bugsubscriptionfilterimportance = SELECT, INSERT
2744+public.bugsubscriptionfiltertag = SELECT, INSERT
2745 public.bugnomination = SELECT
2746 public.bug = SELECT, INSERT, UPDATE
2747 public.bugactivity = SELECT, INSERT
2748@@ -1440,6 +1477,7 @@
2749 [translationsbranchscanner]
2750 type=user
2751 groups=branchscanner,translations_approval
2752+public.translationtemplatesbuild = SELECT, INSERT
2753
2754 [translationstobranch]
2755 type=user
2756@@ -1545,6 +1583,10 @@
2757 public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE
2758 public.bugjob = SELECT, INSERT
2759 public.bugsubscription = SELECT, INSERT
2760+public.bugsubscriptionfilter = SELECT, INSERT
2761+public.bugsubscriptionfilterstatus = SELECT, INSERT
2762+public.bugsubscriptionfilterimportance = SELECT, INSERT
2763+public.bugsubscriptionfiltertag = SELECT, INSERT
2764 public.bugnotification = SELECT, INSERT
2765 public.bugnotificationattachment = SELECT
2766 public.bugnotificationrecipient = SELECT, INSERT
2767@@ -1553,6 +1595,10 @@
2768 public.bugtask = SELECT, INSERT, UPDATE
2769 public.bugmessage = SELECT, INSERT
2770 public.bugsubscription = SELECT, INSERT, UPDATE, DELETE
2771+public.bugsubscriptionfilter = SELECT, INSERT, UPDATE, DELETE
2772+public.bugsubscriptionfilterstatus = SELECT, INSERT, UPDATE, DELETE
2773+public.bugsubscriptionfilterimportance = SELECT, INSERT, UPDATE, DELETE
2774+public.bugsubscriptionfiltertag = SELECT, INSERT, UPDATE, DELETE
2775 public.bugtracker = SELECT, INSERT
2776 public.bugtrackeralias = SELECT, INSERT
2777 public.bugwatch = SELECT, INSERT
2778@@ -1719,6 +1765,7 @@
2779 public.distribution = SELECT
2780 public.distroseries = SELECT
2781 public.emailaddress = SELECT
2782+public.incrementaldiff = SELECT, INSERT
2783 public.job = SELECT, INSERT, UPDATE
2784 public.karmaaction = SELECT
2785 public.karma = SELECT, INSERT
2786@@ -1731,6 +1778,7 @@
2787 public.previewdiff = SELECT, INSERT
2788 public.product = SELECT
2789 public.productseries = SELECT
2790+public.revision = SELECT
2791 public.seriessourcepackagebranch = SELECT
2792 public.sourcepackagename = SELECT
2793 public.staticdiff = SELECT, INSERT
2794@@ -1812,6 +1860,10 @@
2795 public.message = SELECT, INSERT
2796 public.messagechunk = SELECT, INSERT
2797 public.bugsubscription = SELECT, INSERT
2798+public.bugsubscriptionfilter = SELECT, INSERT
2799+public.bugsubscriptionfilterstatus = SELECT, INSERT
2800+public.bugsubscriptionfilterimportance = SELECT, INSERT
2801+public.bugsubscriptionfiltertag = SELECT, INSERT
2802 public.bugmessage = SELECT, INSERT
2803 public.sourcepackagename = SELECT
2804 public.job = SELECT, INSERT, UPDATE
2805@@ -1839,6 +1891,10 @@
2806 public.bug = SELECT, UPDATE
2807 public.bugattachment = SELECT, DELETE
2808 public.bugsubscription = SELECT
2809+public.bugsubscriptionfilter = SELECT
2810+public.bugsubscriptionfilterstatus = SELECT
2811+public.bugsubscriptionfilterimportance = SELECT
2812+public.bugsubscriptionfiltertag = SELECT
2813 public.bugaffectsperson = SELECT
2814 public.bugnotification = SELECT, DELETE
2815 public.bugnotificationrecipientarchive = SELECT
2816
2817=== modified file 'lib/canonical/config/schema-lazr.conf'
2818--- lib/canonical/config/schema-lazr.conf 2010-09-24 14:24:06 +0000
2819+++ lib/canonical/config/schema-lazr.conf 2010-10-04 22:08:24 +0000
2820@@ -301,6 +301,18 @@
2821 # datatype: string
2822 logfile: -
2823
2824+# The location of the log file used by the LaunchpadForkingService
2825+# datatype: string
2826+forker_logfile: -
2827+
2828+# Should we be using the forking daemon? Or should we be calling spawnProcess
2829+# instead?
2830+# datatype: boolean
2831+use_forking_daemon: False
2832+# What disk path will the daemon listen on
2833+# datatype: string
2834+forking_daemon_socket: /var/tmp/launchpad_forking_service.sock
2835+
2836 # The prefix of the web URL for all public branches. This should end with a
2837 # slash.
2838 #
2839@@ -644,6 +656,11 @@
2840 # datatype: integer
2841 storm_cache_size: 500
2842
2843+# Where database/replication/slon_ctl.py dumps its logs. Used for the
2844+# staging replication environment.
2845+# datatype: existing_directory
2846+replication_logdir: database/replication
2847+
2848
2849 [diff]
2850 # The maximum size in bytes to read from the librarian to make available in
2851
2852=== modified file 'lib/canonical/launchpad/icing/style-3-0.css.in'
2853--- lib/canonical/launchpad/icing/style-3-0.css.in 2010-09-10 17:32:29 +0000
2854+++ lib/canonical/launchpad/icing/style-3-0.css.in 2010-10-04 22:08:24 +0000
2855@@ -2419,7 +2419,7 @@
2856 background-repeat: no-repeat;
2857 background-position:right center;
2858 }
2859-table#packages_list tr.superseded {
2860+table#packages_list tr.superseded, tr.blacklisted {
2861 background-color: #eee;
2862 }
2863 ul.latest-ppa-updates li:nth-child(odd) {
2864
2865=== modified file 'lib/canonical/launchpad/interfaces/__init__.py'
2866--- lib/canonical/launchpad/interfaces/__init__.py 2010-09-30 17:08:50 +0000
2867+++ lib/canonical/launchpad/interfaces/__init__.py 2010-10-04 22:08:24 +0000
2868@@ -55,6 +55,8 @@
2869 from lp.registry.interfaces.distribution import *
2870 from lp.registry.interfaces.distributionmirror import *
2871 from lp.registry.interfaces.distributionsourcepackage import *
2872+from lp.registry.interfaces.distroseriesdifference import *
2873+from lp.registry.interfaces.distroseriesdifferencecomment import *
2874 from lp.soyuz.interfaces.distributionsourcepackagecache import *
2875 from lp.soyuz.interfaces.distributionsourcepackagerelease import *
2876 from lp.registry.interfaces.series import *
2877
2878=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
2879--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-08-27 11:19:54 +0000
2880+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-10-04 22:08:24 +0000
2881@@ -77,6 +77,9 @@
2882 IDistributionSourcePackage,
2883 )
2884 from lp.registry.interfaces.distroseries import IDistroSeries
2885+from lp.registry.interfaces.distroseriesdifferencecomment import (
2886+ IDistroSeriesDifferenceComment,
2887+ )
2888 from lp.registry.interfaces.person import (
2889 IPerson,
2890 IPersonPublic,
2891@@ -89,6 +92,7 @@
2892 IStructuralSubscription,
2893 IStructuralSubscriptionTarget,
2894 )
2895+from lp.services.comments.interfaces.conversation import IComment
2896 from lp.soyuz.enums import (
2897 PackagePublishingStatus,
2898 PackageUploadCustomFormat,
2899@@ -321,6 +325,9 @@
2900 IBuildFarmJob['status'].vocabulary = BuildStatus
2901 IBuildFarmJob['buildqueue_record'].schema = IBuildQueue
2902
2903+# IComment
2904+IComment['comment_author'].schema = IPerson
2905+
2906 # IDistribution
2907 IDistribution['series'].value_type.schema = IDistroSeries
2908 patch_reference_property(
2909@@ -366,6 +373,9 @@
2910 IDistroSeries, 'getPackageUploads', IPackageUpload)
2911 patch_reference_property(IDistroSeries, 'parent_series', IDistroSeries)
2912
2913+# IDistroSeriesDifferenceComment
2914+IDistroSeriesDifferenceComment['comment_author'].schema = IPerson
2915+
2916 # IDistroArchSeries
2917 patch_reference_property(IDistroArchSeries, 'main_archive', IArchive)
2918
2919
2920=== modified file 'lib/canonical/launchpad/scripts/runlaunchpad.py'
2921--- lib/canonical/launchpad/scripts/runlaunchpad.py 2010-09-17 20:46:58 +0000
2922+++ lib/canonical/launchpad/scripts/runlaunchpad.py 2010-10-04 22:08:24 +0000
2923@@ -184,6 +184,53 @@
2924 stop_at_exit(process)
2925
2926
2927+class ForkingSessionService(Service):
2928+ """A lp-forking-service for handling ssh access."""
2929+
2930+ # TODO: The SFTP (and bzr+ssh) server depends fairly heavily on this
2931+ # service. It would seem reasonable to make one always start if the
2932+ # other one is started. Though this might be a way to "FeatureFlag"
2933+ # whether this is active or not.
2934+
2935+ @property
2936+ def should_launch(self):
2937+ return (config.codehosting.launch and
2938+ config.codehosting.use_forking_daemon)
2939+
2940+ @property
2941+ def logfile(self):
2942+ """Return the log file to use.
2943+
2944+ Default to the value of the configuration key logfile.
2945+ """
2946+ return config.codehosting.forker_logfile
2947+
2948+ def launch(self):
2949+ # Following the logic in TacFile. Specifically, if you configure sftp
2950+ # to not run (and thus bzr+ssh) then we don't want to run the forking
2951+ # service.
2952+ if not self.should_launch:
2953+ return
2954+ from lp.codehosting import get_bzr_path
2955+ command = [config.root + '/bin/py', get_bzr_path(),
2956+ 'launchpad-forking-service',
2957+ '--path', config.codehosting.forking_daemon_socket,
2958+ ]
2959+ env = dict(os.environ)
2960+ env['BZR_PLUGIN_PATH'] = config.root + '/bzrplugins'
2961+ logfile = self.logfile
2962+ if logfile == '-':
2963+ # This process uses a different logging infrastructure from the
2964+ # rest of the Launchpad code. As such, it cannot trivially use '-'
2965+ # as the logfile. So we just ignore this setting.
2966+ pass
2967+ else:
2968+ env['BZR_LOG'] = logfile
2969+ process = subprocess.Popen(command, env=env, stdin=subprocess.PIPE)
2970+ process.stdin.close()
2971+ stop_at_exit(process)
2972+
2973+
2974 def stop_at_exit(process):
2975 """Create and register an atexit hook for killing a process.
2976
2977@@ -208,6 +255,9 @@
2978 'librarian': TacFile('librarian', 'daemons/librarian.tac',
2979 'librarian_server', prepare_for_librarian),
2980 'sftp': TacFile('sftp', 'daemons/sftp.tac', 'codehosting'),
2981+ # TODO, we probably need to run the forking service whenever somebody
2982+ # requests the sftp service...
2983+ 'forker': ForkingSessionService(),
2984 'mailman': MailmanService(),
2985 'codebrowse': CodebrowseService(),
2986 'google-webservice': GoogleWebService(),
2987
2988=== modified file 'lib/canonical/launchpad/webapp/interfaces.py'
2989--- lib/canonical/launchpad/webapp/interfaces.py 2010-09-12 11:43:36 +0000
2990+++ lib/canonical/launchpad/webapp/interfaces.py 2010-10-04 22:08:24 +0000
2991@@ -335,6 +335,10 @@
2992 query_string_params = Attribute(
2993 'A dictionary of the query string parameters.')
2994
2995+ is_ajax = Bool(
2996+ title=_('Is ajax'), required=False, readonly=True,
2997+ description=_("Indicates whether the request is an XMLHttpRequest."))
2998+
2999 def getRootURL(rootsite):
3000 """Return this request's root URL.
3001
3002
3003=== modified file 'lib/canonical/launchpad/webapp/servers.py'
3004--- lib/canonical/launchpad/webapp/servers.py 2010-09-22 15:17:39 +0000
3005+++ lib/canonical/launchpad/webapp/servers.py 2010-10-04 22:08:24 +0000
3006@@ -526,7 +526,27 @@
3007 return decoded_qs
3008
3009
3010-class BasicLaunchpadRequest:
3011+class LaunchpadBrowserRequestMixin:
3012+ """Provides methods used for both API and web browser requests."""
3013+
3014+ def getRootURL(self, rootsite):
3015+ """See IBasicLaunchpadRequest."""
3016+ if rootsite is not None:
3017+ assert rootsite in allvhosts.configs, (
3018+ "rootsite is %s. Must be in %r." % (
3019+ rootsite, sorted(allvhosts.configs.keys())))
3020+ root_url = allvhosts.configs[rootsite].rooturl
3021+ else:
3022+ root_url = self.getApplicationURL() + '/'
3023+ return root_url
3024+
3025+ @property
3026+ def is_ajax(self):
3027+ """See `IBasicLaunchpadRequest`."""
3028+ return 'XMLHttpRequest' == self.getHeader('HTTP_X_REQUESTED_WITH')
3029+
3030+
3031+class BasicLaunchpadRequest(LaunchpadBrowserRequestMixin):
3032 """Mixin request class to provide stepstogo."""
3033
3034 implements(IBasicLaunchpadRequest)
3035@@ -581,24 +601,8 @@
3036 return get_query_string_params(self)
3037
3038
3039-class LaunchpadBrowserRequestMixin:
3040- """A mixin for classes that share some method implementations."""
3041-
3042- def getRootURL(self, rootsite):
3043- """See IBasicLaunchpadRequest."""
3044- if rootsite is not None:
3045- assert rootsite in allvhosts.configs, (
3046- "rootsite is %s. Must be in %r." % (
3047- rootsite, sorted(allvhosts.configs.keys())))
3048- root_url = allvhosts.configs[rootsite].rooturl
3049- else:
3050- root_url = self.getApplicationURL() + '/'
3051- return root_url
3052-
3053-
3054 class LaunchpadBrowserRequest(BasicLaunchpadRequest, BrowserRequest,
3055- NotificationRequest, ErrorReportRequest,
3056- LaunchpadBrowserRequestMixin):
3057+ NotificationRequest, ErrorReportRequest):
3058 """Integration of launchpad mixin request classes to make an uber
3059 launchpad request class.
3060 """
3061@@ -1370,7 +1374,7 @@
3062
3063
3064 class PublicXMLRPCRequest(BasicLaunchpadRequest, XMLRPCRequest,
3065- ErrorReportRequest, LaunchpadBrowserRequestMixin):
3066+ ErrorReportRequest):
3067 """Request type for doing public XML-RPC in Launchpad."""
3068
3069 def _createResponse(self):
3070
3071=== modified file 'lib/canonical/launchpad/webapp/tests/test_servers.py'
3072--- lib/canonical/launchpad/webapp/tests/test_servers.py 2010-08-20 20:31:18 +0000
3073+++ lib/canonical/launchpad/webapp/tests/test_servers.py 2010-10-04 22:08:24 +0000
3074@@ -358,6 +358,20 @@
3075 retried_request.response.getHeader('Vary'),
3076 'Cookie, Authorization')
3077
3078+ def test_is_ajax_false(self):
3079+ """Normal requests do not define HTTP_X_REQUESTED_WITH."""
3080+ request = LaunchpadBrowserRequest(StringIO.StringIO(''), {})
3081+
3082+ self.assertFalse(request.is_ajax)
3083+
3084+ def test_is_ajax_true(self):
3085+ """Requests with HTTP_X_REQUESTED_WITH set are ajax requests."""
3086+ request = LaunchpadBrowserRequest(StringIO.StringIO(''), {
3087+ 'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest',
3088+ })
3089+
3090+ self.assertTrue(request.is_ajax)
3091+
3092
3093 class IThingSet(Interface):
3094 """Marker interface for a set of things."""
3095
3096=== modified file 'lib/canonical/launchpad/zcml/librarian.zcml'
3097--- lib/canonical/launchpad/zcml/librarian.zcml 2010-09-30 02:52:07 +0000
3098+++ lib/canonical/launchpad/zcml/librarian.zcml 2010-10-04 22:08:24 +0000
3099@@ -16,7 +16,7 @@
3100 <allow interface="canonical.launchpad.interfaces.librarian.ILibraryFileAliasWithParent" />
3101 <require
3102 permission="launchpad.Edit"
3103- set_attributes="restricted" />
3104+ set_attributes="mimetype restricted" />
3105 </class>
3106
3107 <class class="canonical.launchpad.database.librarian.LibraryFileContent">
3108
3109=== modified file 'lib/lp/app/templates/base-layout-macros.pt'
3110--- lib/lp/app/templates/base-layout-macros.pt 2010-09-29 03:26:01 +0000
3111+++ lib/lp/app/templates/base-layout-macros.pt 2010-10-04 22:08:24 +0000
3112@@ -181,6 +181,8 @@
3113 <script type="text/javascript"
3114 tal:attributes="src string:${lp_js}/bugs/bugtracker_overlay.js"></script>
3115 <script type="text/javascript"
3116+ tal:attributes="src string:${lp_js}/registry/distroseriesdifferences_details.js"></script>
3117+ <script type="text/javascript"
3118 tal:attributes="src string:${lp_js}/registry/milestoneoverlay.js"></script>
3119 <script type="text/javascript"
3120 tal:attributes="src string:${lp_js}/registry/milestonetable.js"></script>
3121
3122=== modified file 'lib/lp/bugs/browser/bugattachment.py'
3123--- lib/lp/bugs/browser/bugattachment.py 2010-09-06 07:44:21 +0000
3124+++ lib/lp/bugs/browser/bugattachment.py 2010-10-04 22:08:24 +0000
3125@@ -12,19 +12,21 @@
3126 'BugAttachmentURL',
3127 ]
3128
3129-from cStringIO import StringIO
3130-
3131-from zope.component import getUtility
3132+from zope.component import (
3133+ getMultiAdapter,
3134+ getUtility,
3135+ )
3136 from zope.contenttype import guess_content_type
3137 from zope.interface import implements
3138
3139 from canonical.launchpad.browser.librarian import (
3140 FileNavigationMixin,
3141 ProxiedLibraryFileAlias,
3142- StreamOrRedirectLibraryFileAliasView,
3143 SafeStreamOrRedirectLibraryFileAliasView,
3144 )
3145-from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
3146+from canonical.launchpad.interfaces.librarian import (
3147+ ILibraryFileAliasWithParent,
3148+ )
3149 from canonical.launchpad.webapp import (
3150 canonical_url,
3151 custom_widget,
3152@@ -163,7 +165,10 @@
3153 self.context.title = data['title']
3154
3155 if self.context.libraryfile.mimetype != data['contenttype']:
3156- self.updateContentType(data['contenttype'])
3157+ lfa_with_parent = getMultiAdapter(
3158+ (self.context.libraryfile, self.context),
3159+ ILibraryFileAliasWithParent)
3160+ lfa_with_parent.mimetype = data['contenttype']
3161
3162 @action('Delete Attachment', name='delete')
3163 def delete_action(self, action, data):
3164@@ -174,20 +179,6 @@
3165 url=libraryfile_url, name=self.context.title))
3166 self.context.removeFromBug(user=self.user)
3167
3168- def updateContentType(self, new_content_type):
3169- """Update the attachment content type."""
3170- filealiasset = getUtility(ILibraryFileAliasSet)
3171- old_filealias = self.context.libraryfile
3172- # Download the file and upload it again with the new content
3173- # type.
3174- # XXX: Bjorn Tillenius 2005-06-30:
3175- # It should be possible to simply create a new filealias
3176- # with the same content as the old one.
3177- old_content = old_filealias.read()
3178- self.context.libraryfile = filealiasset.create(
3179- name=old_filealias.filename, size=len(old_content),
3180- file=StringIO(old_content), contentType=new_content_type)
3181-
3182 @property
3183 def label(self):
3184 return smartquote('Bug #%d - Edit attachment "%s"') % (
3185
3186=== added file 'lib/lp/bugs/browser/tests/test_bugattachment_edit_view.py'
3187--- lib/lp/bugs/browser/tests/test_bugattachment_edit_view.py 1970-01-01 00:00:00 +0000
3188+++ lib/lp/bugs/browser/tests/test_bugattachment_edit_view.py 2010-10-04 22:08:24 +0000
3189@@ -0,0 +1,107 @@
3190+# Copyright 2010 Canonical Ltd. This software is licensed under the
3191+# GNU Affero General Public License version 3 (see the file LICENSE).
3192+
3193+__metaclass__ = type
3194+
3195+import transaction
3196+from zope.security.interfaces import Unauthorized
3197+
3198+from canonical.testing import LaunchpadFunctionalLayer
3199+from lp.testing import (
3200+ login_person,
3201+ TestCaseWithFactory,
3202+ )
3203+from lp.testing.views import create_initialized_view
3204+
3205+
3206+class TestBugAttachmentEditView(TestCaseWithFactory):
3207+ """Tests of traversal to and access of files of bug attachments."""
3208+
3209+ layer = LaunchpadFunctionalLayer
3210+
3211+ CHANGE_FORM_DATA = {
3212+ 'field.title': 'new description',
3213+ 'field.patch': 'on',
3214+ 'field.contenttype': 'application/whatever',
3215+ 'field.actions.change': 'Change',
3216+ }
3217+
3218+ def setUp(self):
3219+ super(TestBugAttachmentEditView, self).setUp()
3220+ self.bug_owner = self.factory.makePerson()
3221+ login_person(self.bug_owner)
3222+ self.bug = self.factory.makeBug(owner=self.bug_owner)
3223+ self.bugattachment = self.factory.makeBugAttachment(
3224+ bug=self.bug, filename='foo.diff', data='file content',
3225+ description='attachment description', content_type='text/plain',
3226+ is_patch=False)
3227+ # The Librarian server should know about the new file before
3228+ # we start the tests.
3229+ transaction.commit()
3230+
3231+ def test_change_action_public_bug(self):
3232+ # Properties of attachments for public bugs can be
3233+ # changed by every user.
3234+ user = self.factory.makePerson()
3235+ login_person(user)
3236+ create_initialized_view(
3237+ self.bugattachment, name='+edit', form=self.CHANGE_FORM_DATA)
3238+ self.assertEqual('new description', self.bugattachment.title)
3239+ self.assertTrue(self.bugattachment.is_patch)
3240+ self.assertEqual(
3241+ 'application/whatever', self.bugattachment.libraryfile.mimetype)
3242+
3243+ def test_change_action_private_bug(self):
3244+ # Subscribers of a private bug can edit attachments.
3245+ user = self.factory.makePerson()
3246+ self.bug.subscribe(user, self.bug_owner)
3247+ self.bug.setPrivate(True, self.bug_owner)
3248+ transaction.commit()
3249+ login_person(user)
3250+ create_initialized_view(
3251+ self.bugattachment, name='+edit', form=self.CHANGE_FORM_DATA)
3252+ self.assertEqual('new description', self.bugattachment.title)
3253+ self.assertTrue(self.bugattachment.is_patch)
3254+ self.assertEqual(
3255+ 'application/whatever', self.bugattachment.libraryfile.mimetype)
3256+
3257+ def test_change_action_private_bug_unauthorized(self):
3258+ # Other users cannot edit attachments of private bugs.
3259+ user = self.factory.makePerson()
3260+ self.bug.setPrivate(True, self.bug_owner)
3261+ transaction.commit()
3262+ login_person(user)
3263+ self.assertRaises(
3264+ Unauthorized, create_initialized_view, self.bugattachment,
3265+ name='+edit', form=self.CHANGE_FORM_DATA)
3266+
3267+ DELETE_FORM_DATA = {
3268+ 'field.actions.delete': 'Delete Attachment',
3269+ }
3270+
3271+ def test_delete_action_public_bug(self):
3272+ # Bug attachments can be removed from a bug.
3273+ user = self.factory.makePerson()
3274+ login_person(user)
3275+ create_initialized_view(
3276+ self.bugattachment, name='+edit', form=self.DELETE_FORM_DATA)
3277+ self.assertEqual(0, self.bug.attachments.count())
3278+
3279+ def test_delete_action_private_bug(self):
3280+ # Subscribers of a private bug can delete attachments.
3281+ user = self.factory.makePerson()
3282+ self.bug.subscribe(user, self.bug_owner)
3283+ self.bug.setPrivate(True, self.bug_owner)
3284+ login_person(user)
3285+ create_initialized_view(
3286+ self.bugattachment, name='+edit', form=self.DELETE_FORM_DATA)
3287+ self.assertEqual(0, self.bug.attachments.count())
3288+
3289+ def test_delete_action_private_bug_unautorized(self):
3290+ # Other users cannot delete private bug attachments.
3291+ user = self.factory.makePerson()
3292+ self.bug.setPrivate(True, self.bug_owner)
3293+ login_person(user)
3294+ self.assertRaises(
3295+ Unauthorized, create_initialized_view, self.bugattachment,
3296+ name='+edit', form=self.DELETE_FORM_DATA)
3297
3298=== modified file 'lib/lp/bugs/configure.zcml'
3299--- lib/lp/bugs/configure.zcml 2010-09-28 14:59:25 +0000
3300+++ lib/lp/bugs/configure.zcml 2010-10-04 22:08:24 +0000
3301@@ -368,10 +368,14 @@
3302 aliases
3303 baseurl
3304 bugtrackertype
3305+ componentForDistroSourcePackage
3306+ component_groups
3307 contactdetails
3308+ getAllRemoteComponentGroups
3309 getBugFilingAndSearchLinks
3310 getBugsWatching
3311 getLinkedPersonByName
3312+ getRemoteComponentGroup
3313 has_lp_plugin
3314 id
3315 imported_bug_messages
3316@@ -394,6 +398,7 @@
3317 destroySelf
3318 ensurePersonForSelf
3319 linkPersonToSelf
3320+ addRemoteComponentGroup
3321 "
3322 set_attributes="
3323 aliases
3324@@ -461,6 +466,47 @@
3325 interface="lp.bugs.interfaces.bugtracker.IBugTrackerAliasSet"/>
3326 </securedutility>
3327
3328+ <!--BugTrackerComponent -->
3329+
3330+ <class
3331+ class="lp.bugs.model.bugtracker.BugTrackerComponent">
3332+ <require
3333+ permission="zope.Public"
3334+ attributes="
3335+ id
3336+ name
3337+ is_visible
3338+ is_custom
3339+ "/>
3340+ <require
3341+ permission="launchpad.AnyPerson"
3342+ set_attributes="
3343+ is_visible
3344+ is_custom
3345+ "/>
3346+ <implements
3347+ interface="lp.bugs.interfaces.bugtracker.IBugTrackerComponent"/>
3348+ </class>
3349+
3350+ <!--BugTrackerComponentGroup -->
3351+
3352+ <class
3353+ class="lp.bugs.model.bugtracker.BugTrackerComponentGroup">
3354+ <require
3355+ permission="zope.Public"
3356+ attributes="
3357+ id
3358+ name
3359+ bug_tracker
3360+ components
3361+ getComponent
3362+ addComponent
3363+ addCustomComponent
3364+ "/>
3365+ <implements
3366+ interface="lp.bugs.interfaces.bugtracker.IBugTrackerComponentGroup"/>
3367+ </class>
3368+
3369 <!-- RemoteBug -->
3370
3371 <class
3372
3373=== modified file 'lib/lp/bugs/doc/initial-bug-contacts.txt'
3374--- lib/lp/bugs/doc/initial-bug-contacts.txt 2010-08-20 01:29:08 +0000
3375+++ lib/lp/bugs/doc/initial-bug-contacts.txt 2010-10-04 22:08:24 +0000
3376@@ -16,7 +16,7 @@
3377 >>> debian = getUtility(IDistributionSet).getByName("debian")
3378 >>> debian_firefox = debian.getSourcePackage("mozilla-firefox")
3379
3380- >>> debian_firefox.bug_subscriptions
3381+ >>> list(debian_firefox.bug_subscriptions)
3382 []
3383
3384 Adding a package subscription is done with the
3385
3386=== modified file 'lib/lp/bugs/interfaces/bugtracker.py'
3387--- lib/lp/bugs/interfaces/bugtracker.py 2010-09-19 23:52:49 +0000
3388+++ lib/lp/bugs/interfaces/bugtracker.py 2010-10-04 22:08:24 +0000
3389@@ -12,6 +12,8 @@
3390 'IBugTracker',
3391 'IBugTrackerAlias',
3392 'IBugTrackerAliasSet',
3393+ 'IBugTrackerComponent',
3394+ 'IBugTrackerComponentGroup',
3395 'IBugTrackerSet',
3396 'IRemoteBug',
3397 'SINGLE_PRODUCT_BUGTRACKERTYPES',
3398@@ -29,6 +31,7 @@
3399 export_as_webservice_entry,
3400 export_factory_operation,
3401 export_read_operation,
3402+ export_write_operation,
3403 exported,
3404 operation_parameters,
3405 operation_returns_entry,
3406@@ -353,6 +356,26 @@
3407 point between now and 24 hours hence.
3408 """
3409
3410+ @operation_parameters(
3411+ component_group_name=TextLine(
3412+ title=u"The name of the remote component group", required=True))
3413+ @export_write_operation()
3414+ def addRemoteComponentGroup(component_group_name):
3415+ """Adds a new component group to the bug tracker"""
3416+
3417+ def getAllRemoteComponentGroups():
3418+ """Return collection of all component groups for this bug tracker"""
3419+
3420+ @operation_parameters(
3421+ component_group_name=TextLine(
3422+ title=u"The name of the remote component group", required=True))
3423+ @export_read_operation()
3424+ def getRemoteComponentGroup(component_group_name):
3425+ """Retrieve a given component group registered with the bug tracker.
3426+
3427+ :param component_group_name: Name of the component group to retrieve.
3428+ """
3429+
3430
3431 class IBugTrackerSet(Interface):
3432 """A set of IBugTracker's.
3433@@ -465,6 +488,72 @@
3434 """Query IBugTrackerAliases by BugTracker."""
3435
3436
3437+class IBugTrackerComponent(Interface):
3438+ """The software component in the remote bug tracker.
3439+
3440+ Most bug trackers organize bug reports by the software 'component'
3441+ they affect. This class provides a mapping of this upstream component
3442+ to the corresponding source package in the distro.
3443+ """
3444+ export_as_webservice_entry()
3445+
3446+ id = Int(title=_('ID'), required=True, readonly=True)
3447+ is_visible = Bool(
3448+ title=_('Is Visible?'),
3449+ description=_("Should the component be shown in "
3450+ "the Launchpad web interface?"),
3451+ readonly=True)
3452+ is_custom = Bool(
3453+ title=_('Is Custom?'),
3454+ description=_("Was the component added locally in "
3455+ "Launchpad? If it was, we must retain "
3456+ "it across updates of bugtracker data."),
3457+ readonly=True)
3458+
3459+ name = exported(
3460+ Text(
3461+ title=_('Name'),
3462+ constraint=name_validator,
3463+ description=_('The name of a software component'
3464+ 'in a remote bug tracker')))
3465+
3466+# XXX: Bug 644794 will implement this link
3467+# distro_source_package = exported(
3468+# Reference(
3469+# title=_('Distribution Source Package'),
3470+# schema=Interface,
3471+# description=_('The distribution source package for this '
3472+# 'component, if one has been defined.')))
3473+
3474+
3475+class IBugTrackerComponentGroup(Interface):
3476+ """A collection of components in a remote bug tracker.
3477+
3478+ Some bug trackers organize sets of components into higher level groups,
3479+ such as Bugzilla's 'product'.
3480+ """
3481+ export_as_webservice_entry()
3482+
3483+ id = Int(title=_('ID'))
3484+ name = exported(
3485+ Text(
3486+ title=_('Name'),
3487+ constraint=name_validator,
3488+ description=_('The name of the bug tracker product.')))
3489+ components = exported(
3490+ Reference(title=_('Components'), schema=IBugTrackerComponent))
3491+ bug_tracker = exported(
3492+ Reference(title=_('BugTracker'), schema=IBugTracker))
3493+
3494+ @operation_parameters(
3495+ component_name=TextLine(
3496+ title=u"The name of the remote software component to be added",
3497+ required=True))
3498+ @export_write_operation()
3499+ def addComponent(component_name):
3500+ """Adds a component to be tracked as part of this component group"""
3501+
3502+
3503 class IRemoteBug(Interface):
3504 """A remote bug for a given bug tracker."""
3505
3506
3507=== modified file 'lib/lp/bugs/model/bugmessage.py'
3508--- lib/lp/bugs/model/bugmessage.py 2010-08-20 20:31:18 +0000
3509+++ lib/lp/bugs/model/bugmessage.py 2010-10-04 22:08:24 +0000
3510@@ -44,6 +44,8 @@
3511 notNull=False, default=None)
3512 remote_comment_id = StringCol(notNull=False, default=None)
3513 visible = BoolCol(notNull=True, default=True)
3514+ # -- Uncomment when deployed.
3515+ # index = IntCol()
3516
3517
3518 class BugMessageSet:
3519
3520=== added file 'lib/lp/bugs/model/bugsubscriptionfilter.py'
3521--- lib/lp/bugs/model/bugsubscriptionfilter.py 1970-01-01 00:00:00 +0000
3522+++ lib/lp/bugs/model/bugsubscriptionfilter.py 2010-10-04 22:08:24 +0000
3523@@ -0,0 +1,36 @@
3524+# Copyright 2009 Canonical Ltd. This software is licensed under the
3525+# GNU Affero General Public License version 3 (see the file LICENSE).
3526+
3527+# pylint: disable-msg=E0611,W0212
3528+
3529+__metaclass__ = type
3530+__all__ = ['BugSubscriptionFilter']
3531+
3532+from storm.base import Storm
3533+from storm.locals import (
3534+ Bool,
3535+ Int,
3536+ Reference,
3537+ Unicode,
3538+ )
3539+
3540+
3541+class BugSubscriptionFilter(Storm):
3542+ """A filter to specialize a *structural* subscription."""
3543+
3544+ __storm_table__ = "BugSubscriptionFilter"
3545+
3546+ id = Int(primary=True)
3547+
3548+ structural_subscription_id = Int(
3549+ "structuralsubscription", allow_none=False)
3550+ structural_subscription = Reference(
3551+ structural_subscription_id, "StructuralSubscription.id")
3552+
3553+ find_all_tags = Bool(allow_none=False, default=False)
3554+ include_any_tags = Bool(allow_none=False, default=False)
3555+ exclude_any_tags = Bool(allow_none=False, default=False)
3556+
3557+ other_parameters = Unicode()
3558+
3559+ description = Unicode()
3560
3561=== added file 'lib/lp/bugs/model/bugsubscriptionfilterimportance.py'
3562--- lib/lp/bugs/model/bugsubscriptionfilterimportance.py 1970-01-01 00:00:00 +0000
3563+++ lib/lp/bugs/model/bugsubscriptionfilterimportance.py 2010-10-04 22:08:24 +0000
3564@@ -0,0 +1,29 @@
3565+# Copyright 2009 Canonical Ltd. This software is licensed under the
3566+# GNU Affero General Public License version 3 (see the file LICENSE).
3567+
3568+# pylint: disable-msg=E0611,W0212
3569+
3570+__metaclass__ = type
3571+__all__ = ['BugSubscriptionFilterImportance']
3572+
3573+from storm.base import Storm
3574+from storm.locals import (
3575+ Int,
3576+ Reference,
3577+ )
3578+
3579+from canonical.database.enumcol import DBEnum
3580+from lp.bugs.interfaces.bugtask import BugTaskImportance
3581+
3582+
3583+class BugSubscriptionFilterImportance(Storm):
3584+ """Importances to filter."""
3585+
3586+ __storm_table__ = "BugSubscriptionFilterImportance"
3587+
3588+ id = Int(primary=True)
3589+
3590+ filter_id = Int("filter", allow_none=False)
3591+ filter = Reference(filter_id, "BugSubscriptionFilter.id")
3592+
3593+ importance = DBEnum(enum=BugTaskImportance, allow_none=False)
3594
3595=== added file 'lib/lp/bugs/model/bugsubscriptionfilterstatus.py'
3596--- lib/lp/bugs/model/bugsubscriptionfilterstatus.py 1970-01-01 00:00:00 +0000
3597+++ lib/lp/bugs/model/bugsubscriptionfilterstatus.py 2010-10-04 22:08:24 +0000
3598@@ -0,0 +1,29 @@
3599+# Copyright 2009 Canonical Ltd. This software is licensed under the
3600+# GNU Affero General Public License version 3 (see the file LICENSE).
3601+
3602+# pylint: disable-msg=E0611,W0212
3603+
3604+__metaclass__ = type
3605+__all__ = ['BugSubscriptionFilterStatus']
3606+
3607+from storm.base import Storm
3608+from storm.locals import (
3609+ Int,
3610+ Reference,
3611+ )
3612+
3613+from canonical.database.enumcol import DBEnum
3614+from lp.bugs.interfaces.bugtask import BugTaskStatus
3615+
3616+
3617+class BugSubscriptionFilterStatus(Storm):
3618+ """Statuses to filter."""
3619+
3620+ __storm_table__ = "BugSubscriptionFilterStatus"
3621+
3622+ id = Int(primary=True)
3623+
3624+ filter_id = Int("filter", allow_none=False)
3625+ filter = Reference(filter_id, "BugSubscriptionFilter.id")
3626+
3627+ status = DBEnum(enum=BugTaskStatus, allow_none=False)
3628
3629=== added file 'lib/lp/bugs/model/bugsubscriptionfiltertag.py'
3630--- lib/lp/bugs/model/bugsubscriptionfiltertag.py 1970-01-01 00:00:00 +0000
3631+++ lib/lp/bugs/model/bugsubscriptionfiltertag.py 2010-10-04 22:08:24 +0000
3632@@ -0,0 +1,29 @@
3633+# Copyright 2009 Canonical Ltd. This software is licensed under the
3634+# GNU Affero General Public License version 3 (see the file LICENSE).
3635+
3636+# pylint: disable-msg=E0611,W0212
3637+
3638+__metaclass__ = type
3639+__all__ = ['BugSubscriptionFilterTag']
3640+
3641+from storm.base import Storm
3642+from storm.locals import (
3643+ Bool,
3644+ Int,
3645+ Reference,
3646+ Unicode,
3647+ )
3648+
3649+
3650+class BugSubscriptionFilterTag(Storm):
3651+ """Tags to filter."""
3652+
3653+ __storm_table__ = "BugSubscriptionFilterTag"
3654+
3655+ id = Int(primary=True)
3656+
3657+ filter_id = Int("filter", allow_none=False)
3658+ filter = Reference(filter_id, "BugSubscriptionFilter.id")
3659+
3660+ include = Bool(allow_none=False)
3661+ tag = Unicode(allow_none=False)
3662
3663=== modified file 'lib/lp/bugs/model/bugtracker.py'
3664--- lib/lp/bugs/model/bugtracker.py 2010-09-19 00:35:22 +0000
3665+++ lib/lp/bugs/model/bugtracker.py 2010-10-04 22:08:24 +0000
3666@@ -6,12 +6,14 @@
3667 __metaclass__ = type
3668 __all__ = [
3669 'BugTracker',
3670+ 'BugTrackerSet',
3671 'BugTrackerAlias',
3672 'BugTrackerAliasSet',
3673+ 'BugTrackerComponent',
3674+ 'BugTrackerComponentGroup',
3675 'BugTrackerSet',
3676 ]
3677
3678-
3679 from datetime import datetime
3680 from itertools import chain
3681 # splittype is not formally documented, but is in urllib.__all__, is
3682@@ -22,9 +24,15 @@
3683 splittype,
3684 )
3685
3686+from storm.base import Storm
3687+from storm.locals import (
3688+ Int,
3689+ Reference,
3690+ ReferenceSet,
3691+ Unicode,
3692+ )
3693 from zope.component import getUtility
3694 from zope.interface import implements
3695-from zope.security.interfaces import Unauthorized
3696
3697 from lazr.uri import URI
3698 from pytz import timezone
3699@@ -45,8 +53,6 @@
3700 )
3701 from storm.locals import Bool
3702 from storm.store import Store
3703-from zope.component import getUtility
3704-from zope.interface import implements
3705
3706 from canonical.database.enumcol import EnumCol
3707 from canonical.database.sqlbase import (
3708@@ -64,6 +70,8 @@
3709 IBugTracker,
3710 IBugTrackerAlias,
3711 IBugTrackerAliasSet,
3712+ IBugTrackerComponent,
3713+ IBugTrackerComponentGroup,
3714 IBugTrackerSet,
3715 SINGLE_PRODUCT_BUGTRACKERTYPES,
3716 )
3717@@ -518,6 +526,41 @@
3718 next_check=new_next_check, lastchecked=None,
3719 last_error_type=None)
3720
3721+ def addRemoteComponentGroup(self, component_group_name):
3722+ """See `IBugTracker`."""
3723+
3724+ if component_group_name is None:
3725+ component_group_name = "default"
3726+ component_group = BugTrackerComponentGroup()
3727+ component_group.name = component_group_name
3728+ component_group.bug_tracker = self
3729+
3730+ store = IStore(BugTrackerComponentGroup)
3731+ store.add(component_group)
3732+ store.commit()
3733+
3734+ return component_group
3735+
3736+ def getAllRemoteComponentGroups(self):
3737+ """See `IBugTracker`."""
3738+ component_groups = []
3739+
3740+ component_groups = Store.of(self).find(
3741+ BugTrackerComponentGroup,
3742+ BugTrackerComponentGroup.bug_tracker == self.id)
3743+ component_groups = component_groups.order_by(
3744+ BugTrackerComponentGroup.name)
3745+ return component_groups
3746+
3747+ def getRemoteComponentGroup(self, component_group_name):
3748+ """See `IBugTracker`."""
3749+ component_group = None
3750+ store = IStore(BugTrackerComponentGroup)
3751+ component_group = store.find(
3752+ BugTrackerComponentGroup,
3753+ name = component_group_name).one()
3754+ return component_group
3755+
3756
3757 class BugTrackerSet:
3758 """Implements IBugTrackerSet for a container or set of BugTrackers,
3759@@ -627,9 +670,11 @@
3760
3761 def getMostActiveBugTrackers(self, limit=None):
3762 """See `IBugTrackerSet`."""
3763- store = IStore(self.table)
3764- result = store.find(self.table, self.table.id == BugWatch.bugtrackerID)
3765- result = result.group_by(self.table)
3766+ store = IStore(BugTracker)
3767+ result = store.find(
3768+ BugTracker,
3769+ BugTracker.id == BugWatch.bugtrackerID)
3770+ result = result.group_by(BugTracker)
3771 result = result.order_by(Desc(Count(BugWatch)))
3772 if limit is not None:
3773 return result[:limit]
3774@@ -671,3 +716,91 @@
3775 def queryByBugTracker(self, bugtracker):
3776 """See IBugTrackerSet."""
3777 return self.table.selectBy(bugtracker=bugtracker.id)
3778+
3779+
3780+class BugTrackerComponent(Storm):
3781+ """The software component in the remote bug tracker.
3782+
3783+ Most bug trackers organize bug reports by the software 'component'
3784+ they affect. This class provides a mapping of this upstream component
3785+ to the corresponding source package in the distro.
3786+ """
3787+ implements(IBugTrackerComponent)
3788+ __storm_table__ = 'BugTrackerComponent'
3789+
3790+ id = Int(primary=True)
3791+ name = Unicode(allow_none=False)
3792+
3793+ component_group_id = Int('component_group')
3794+ component_group = Reference(
3795+ component_group_id,
3796+ 'BugTrackerComponentGroup.id')
3797+
3798+ is_visible = Bool(allow_none=False)
3799+ is_custom = Bool(allow_none=False)
3800+
3801+ distro_source_package_id = Int('distro_source_package')
3802+ distro_source_package = Reference(
3803+ distro_source_package_id,
3804+ 'DistributionSourcePackageInDatabase.id')
3805+
3806+
3807+class BugTrackerComponentGroup(Storm):
3808+ """A collection of components in a remote bug tracker.
3809+
3810+ Some bug trackers organize sets of components into higher level groups,
3811+ such as Bugzilla's 'product'.
3812+ """
3813+ implements(IBugTrackerComponentGroup)
3814+ __storm_table__ = 'BugTrackerComponentGroup'
3815+
3816+ id = Int(primary=True)
3817+ name = Unicode(allow_none=False)
3818+ bug_tracker_id = Int('bug_tracker')
3819+ bug_tracker = Reference(bug_tracker_id, 'BugTracker.id')
3820+ components = ReferenceSet(
3821+ id,
3822+ BugTrackerComponent.component_group_id,
3823+ order_by=BugTrackerComponent.name)
3824+
3825+ def addComponent(self, component_name):
3826+ """Adds a component that is synced from a remote bug tracker"""
3827+
3828+ component = BugTrackerComponent()
3829+ component.name = component_name
3830+ component.component_group = self
3831+
3832+ store = IStore(BugTrackerComponent)
3833+ store.add(component)
3834+ store.flush()
3835+
3836+ return component
3837+
3838+ def getComponent(self, component_name):
3839+ """Retrieves a component by the given name.
3840+
3841+ None is returned if there is no component by that name in the
3842+ group.
3843+ """
3844+
3845+ if component_name is None:
3846+ return None
3847+ else:
3848+ return Store.of(self).find(
3849+ BugTrackerComponent,
3850+ (BugTrackerComponent.name == component_name)).one()
3851+
3852+ def addCustomComponent(self, component_name):
3853+ """Adds a component locally that isn't synced from a remote tracker
3854+ """
3855+
3856+ component = BugTrackerComponent()
3857+ component.name = component_name
3858+ component.component_group = self
3859+ component.is_custom = True
3860+
3861+ store = IStore(BugTrackerComponent)
3862+ store.add(component)
3863+ store.flush()
3864+
3865+ return component
3866
3867=== added directory 'lib/lp/bugs/model/tests'
3868=== added file 'lib/lp/bugs/model/tests/__init__.py'
3869=== added file 'lib/lp/bugs/model/tests/test_bugsubscriptionfilter.py'
3870--- lib/lp/bugs/model/tests/test_bugsubscriptionfilter.py 1970-01-01 00:00:00 +0000
3871+++ lib/lp/bugs/model/tests/test_bugsubscriptionfilter.py 2010-10-04 22:08:24 +0000
3872@@ -0,0 +1,66 @@
3873+# Copyright 2010 Canonical Ltd. This software is licensed under the
3874+# GNU Affero General Public License version 3 (see the file LICENSE).
3875+
3876+"""Tests for the bugsubscription module."""
3877+
3878+__metaclass__ = type
3879+
3880+from canonical.launchpad.interfaces.lpstorm import IStore
3881+from canonical.testing import DatabaseFunctionalLayer
3882+from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilter
3883+from lp.testing import (
3884+ login_person,
3885+ TestCaseWithFactory,
3886+ )
3887+
3888+
3889+class TestBugSubscriptionFilter(TestCaseWithFactory):
3890+
3891+ layer = DatabaseFunctionalLayer
3892+
3893+ def setUp(self):
3894+ super(TestBugSubscriptionFilter, self).setUp()
3895+ self.target = self.factory.makeProduct()
3896+ self.subscriber = self.target.owner
3897+ login_person(self.subscriber)
3898+ self.subscription = self.target.addBugSubscription(
3899+ self.subscriber, self.subscriber)
3900+
3901+ def test_basics(self):
3902+ """Test the basic operation of `BugSubscriptionFilter` objects."""
3903+ # Create.
3904+ bug_subscription_filter = BugSubscriptionFilter()
3905+ bug_subscription_filter.structural_subscription = self.subscription
3906+ bug_subscription_filter.find_all_tags = True
3907+ bug_subscription_filter.include_any_tags = True
3908+ bug_subscription_filter.exclude_any_tags = True
3909+ bug_subscription_filter.other_parameters = u"foo"
3910+ bug_subscription_filter.description = u"bar"
3911+ # Flush and reload.
3912+ IStore(bug_subscription_filter).flush()
3913+ IStore(bug_subscription_filter).reload(bug_subscription_filter)
3914+ # Check.
3915+ self.assertIsNot(None, bug_subscription_filter.id)
3916+ self.assertEqual(
3917+ self.subscription.id,
3918+ bug_subscription_filter.structural_subscription_id)
3919+ self.assertEqual(
3920+ self.subscription,
3921+ bug_subscription_filter.structural_subscription)
3922+ self.assertIs(True, bug_subscription_filter.find_all_tags)
3923+ self.assertIs(True, bug_subscription_filter.include_any_tags)
3924+ self.assertIs(True, bug_subscription_filter.exclude_any_tags)
3925+ self.assertEqual(u"foo", bug_subscription_filter.other_parameters)
3926+ self.assertEqual(u"bar", bug_subscription_filter.description)
3927+
3928+ def test_defaults(self):
3929+ """Test the default values of `BugSubscriptionFilter` objects."""
3930+ # Create.
3931+ bug_subscription_filter = BugSubscriptionFilter()
3932+ bug_subscription_filter.structural_subscription = self.subscription
3933+ # Check.
3934+ self.assertIs(False, bug_subscription_filter.find_all_tags)
3935+ self.assertIs(False, bug_subscription_filter.include_any_tags)
3936+ self.assertIs(False, bug_subscription_filter.exclude_any_tags)
3937+ self.assertIs(None, bug_subscription_filter.other_parameters)
3938+ self.assertIs(None, bug_subscription_filter.description)
3939
3940=== added file 'lib/lp/bugs/model/tests/test_bugsubscriptionfilterimportance.py'
3941--- lib/lp/bugs/model/tests/test_bugsubscriptionfilterimportance.py 1970-01-01 00:00:00 +0000
3942+++ lib/lp/bugs/model/tests/test_bugsubscriptionfilterimportance.py 2010-10-04 22:08:24 +0000
3943@@ -0,0 +1,54 @@
3944+# Copyright 2010 Canonical Ltd. This software is licensed under the
3945+# GNU Affero General Public License version 3 (see the file LICENSE).
3946+
3947+"""Tests for the bugsubscription module."""
3948+
3949+__metaclass__ = type
3950+
3951+from canonical.launchpad.interfaces.lpstorm import IStore
3952+from canonical.testing import DatabaseFunctionalLayer
3953+from lp.bugs.interfaces.bugtask import BugTaskImportance
3954+from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilter
3955+from lp.bugs.model.bugsubscriptionfilterimportance import (
3956+ BugSubscriptionFilterImportance,
3957+ )
3958+from lp.testing import (
3959+ login_person,
3960+ TestCaseWithFactory,
3961+ )
3962+
3963+
3964+class TestBugSubscriptionFilterImportance(TestCaseWithFactory):
3965+
3966+ layer = DatabaseFunctionalLayer
3967+
3968+ def setUp(self):
3969+ super(TestBugSubscriptionFilterImportance, self).setUp()
3970+ self.target = self.factory.makeProduct()
3971+ self.subscriber = self.target.owner
3972+ login_person(self.subscriber)
3973+ self.subscription = self.target.addBugSubscription(
3974+ self.subscriber, self.subscriber)
3975+ self.subscription_filter = BugSubscriptionFilter()
3976+ self.subscription_filter.structural_subscription = self.subscription
3977+
3978+ def test_basics(self):
3979+ """Test the basics of `BugSubscriptionFilterImportance` objects."""
3980+ # Create.
3981+ bug_sub_filter_importance = BugSubscriptionFilterImportance()
3982+ bug_sub_filter_importance.filter = self.subscription_filter
3983+ bug_sub_filter_importance.importance = BugTaskImportance.HIGH
3984+ # Flush and reload.
3985+ IStore(bug_sub_filter_importance).flush()
3986+ IStore(bug_sub_filter_importance).reload(bug_sub_filter_importance)
3987+ # Check.
3988+ self.assertIsNot(None, bug_sub_filter_importance.id)
3989+ self.assertEqual(
3990+ self.subscription_filter.id,
3991+ bug_sub_filter_importance.filter_id)
3992+ self.assertEqual(
3993+ self.subscription_filter,
3994+ bug_sub_filter_importance.filter)
3995+ self.assertEqual(
3996+ BugTaskImportance.HIGH,
3997+ bug_sub_filter_importance.importance)
3998
3999=== added file 'lib/lp/bugs/model/tests/test_bugsubscriptionfilterstatus.py'
4000--- lib/lp/bugs/model/tests/test_bugsubscriptionfilterstatus.py 1970-01-01 00:00:00 +0000
4001+++ lib/lp/bugs/model/tests/test_bugsubscriptionfilterstatus.py 2010-10-04 22:08:24 +0000
4002@@ -0,0 +1,52 @@
4003+# Copyright 2010 Canonical Ltd. This software is licensed under the
4004+# GNU Affero General Public License version 3 (see the file LICENSE).
4005+
4006+"""Tests for the bugsubscription module."""
4007+
4008+__metaclass__ = type
4009+
4010+from canonical.launchpad.interfaces.lpstorm import IStore
4011+from canonical.testing import DatabaseFunctionalLayer
4012+from lp.bugs.interfaces.bugtask import BugTaskStatus
4013+from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilter
4014+from lp.bugs.model.bugsubscriptionfilterstatus import (
4015+ BugSubscriptionFilterStatus,
4016+ )
4017+from lp.testing import (
4018+ login_person,
4019+ TestCaseWithFactory,
4020+ )
4021+
4022+
4023+class TestBugSubscriptionFilterStatus(TestCaseWithFactory):
4024+
4025+ layer = DatabaseFunctionalLayer
4026+
4027+ def setUp(self):
4028+ super(TestBugSubscriptionFilterStatus, self).setUp()
4029+ self.target = self.factory.makeProduct()
4030+ self.subscriber = self.target.owner
4031+ login_person(self.subscriber)
4032+ self.subscription = self.target.addBugSubscription(
4033+ self.subscriber, self.subscriber)
4034+ self.subscription_filter = BugSubscriptionFilter()
4035+ self.subscription_filter.structural_subscription = self.subscription
4036+
4037+ def test_basics(self):
4038+ """Test the basics of `BugSubscriptionFilterStatus` objects."""
4039+ # Create.
4040+ bug_sub_filter_status = BugSubscriptionFilterStatus()
4041+ bug_sub_filter_status.filter = self.subscription_filter
4042+ bug_sub_filter_status.status = BugTaskStatus.NEW
4043+ # Flush and reload.
4044+ IStore(bug_sub_filter_status).flush()
4045+ IStore(bug_sub_filter_status).reload(bug_sub_filter_status)
4046+ # Check.
4047+ self.assertIsNot(None, bug_sub_filter_status.id)
4048+ self.assertEqual(
4049+ self.subscription_filter.id,
4050+ bug_sub_filter_status.filter_id)
4051+ self.assertEqual(
4052+ self.subscription_filter,
4053+ bug_sub_filter_status.filter)
4054+ self.assertEqual(BugTaskStatus.NEW, bug_sub_filter_status.status)
4055
4056=== added file 'lib/lp/bugs/model/tests/test_bugsubscriptionfiltertag.py'
4057--- lib/lp/bugs/model/tests/test_bugsubscriptionfiltertag.py 1970-01-01 00:00:00 +0000
4058+++ lib/lp/bugs/model/tests/test_bugsubscriptionfiltertag.py 2010-10-04 22:08:24 +0000
4059@@ -0,0 +1,51 @@
4060+# Copyright 2010 Canonical Ltd. This software is licensed under the
4061+# GNU Affero General Public License version 3 (see the file LICENSE).
4062+
4063+"""Tests for the bugsubscription module."""
4064+
4065+__metaclass__ = type
4066+
4067+from canonical.launchpad.interfaces.lpstorm import IStore
4068+from canonical.testing import DatabaseFunctionalLayer
4069+from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilter
4070+from lp.bugs.model.bugsubscriptionfiltertag import BugSubscriptionFilterTag
4071+from lp.testing import (
4072+ login_person,
4073+ TestCaseWithFactory,
4074+ )
4075+
4076+
4077+class TestBugSubscriptionFilterTag(TestCaseWithFactory):
4078+
4079+ layer = DatabaseFunctionalLayer
4080+
4081+ def setUp(self):
4082+ super(TestBugSubscriptionFilterTag, self).setUp()
4083+ self.target = self.factory.makeProduct()
4084+ self.subscriber = self.target.owner
4085+ login_person(self.subscriber)
4086+ self.subscription = self.target.addBugSubscription(
4087+ self.subscriber, self.subscriber)
4088+ self.subscription_filter = BugSubscriptionFilter()
4089+ self.subscription_filter.structural_subscription = self.subscription
4090+
4091+ def test_basics(self):
4092+ """Test the basics of `BugSubscriptionFilterTag` objects."""
4093+ # Create.
4094+ bug_sub_filter_tag = BugSubscriptionFilterTag()
4095+ bug_sub_filter_tag.filter = self.subscription_filter
4096+ bug_sub_filter_tag.include = True
4097+ bug_sub_filter_tag.tag = u"foo"
4098+ # Flush and reload.
4099+ IStore(bug_sub_filter_tag).flush()
4100+ IStore(bug_sub_filter_tag).reload(bug_sub_filter_tag)
4101+ # Check.
4102+ self.assertIsNot(None, bug_sub_filter_tag.id)
4103+ self.assertEqual(
4104+ self.subscription_filter.id,
4105+ bug_sub_filter_tag.filter_id)
4106+ self.assertEqual(
4107+ self.subscription_filter,
4108+ bug_sub_filter_tag.filter)
4109+ self.assertIs(True, bug_sub_filter_tag.include)
4110+ self.assertEqual(u"foo", bug_sub_filter_tag.tag)
4111
4112=== modified file 'lib/lp/bugs/tests/has-bug-supervisor.txt'
4113--- lib/lp/bugs/tests/has-bug-supervisor.txt 2009-08-25 11:21:05 +0000
4114+++ lib/lp/bugs/tests/has-bug-supervisor.txt 2010-10-04 22:08:24 +0000
4115@@ -5,7 +5,7 @@
4116 structural subscription targets. When the bug supervisor for such an object
4117 is set, a new bug subscription is created as well.
4118
4119- >>> target.bug_subscriptions
4120+ >>> list(target.bug_subscriptions)
4121 []
4122
4123 >>> print target.bug_supervisor
4124
4125=== added file 'lib/lp/bugs/tests/test_bugtracker_components.py'
4126--- lib/lp/bugs/tests/test_bugtracker_components.py 1970-01-01 00:00:00 +0000
4127+++ lib/lp/bugs/tests/test_bugtracker_components.py 2010-10-04 22:08:24 +0000
4128@@ -0,0 +1,176 @@
4129+# Copyright 2010 Canonical Ltd. This software is licensed under the
4130+# GNU Affero General Public License version 3 (see the file LICENSE).
4131+
4132+"""Test for components and component groups (products) in bug trackers."""
4133+
4134+__metaclass__ = type
4135+
4136+__all__ = []
4137+
4138+import unittest
4139+
4140+from canonical.launchpad.ftests import login_person
4141+from canonical.testing import DatabaseFunctionalLayer
4142+from lp.testing import TestCaseWithFactory
4143+
4144+class TestBugTrackerComponent(TestCaseWithFactory):
4145+
4146+ layer = DatabaseFunctionalLayer
4147+
4148+ def setUp(self):
4149+ super(TestBugTrackerComponent, self).setUp()
4150+
4151+ regular_user = self.factory.makePerson()
4152+ login_person(regular_user)
4153+
4154+ self.bug_tracker = self.factory.makeBugTracker()
4155+
4156+ self.comp_group = self.factory.makeBugTrackerComponentGroup(
4157+ u'alpha',
4158+ self.bug_tracker)
4159+
4160+ def test_component_creation(self):
4161+ """Verify a component can be created"""
4162+ component = self.factory.makeBugTrackerComponent(
4163+ u'example', self.comp_group)
4164+ self.assertTrue(component is not None)
4165+ self.assertEqual(component.name, u'example')
4166+
4167+ def test_set_visibility(self):
4168+ """Users can delete components
4169+
4170+ In case invalid components get imported from a remote bug
4171+ tracker, users can hide them so they don't show up in the UI.
4172+ We do this rather than delete them outright so that they won't
4173+ show up again when we re-sync from the remote bug tracker.
4174+ """
4175+ component = self.factory.makeBugTrackerComponent(
4176+ u'example', self.comp_group)
4177+ self.assertEqual(component.is_visible, True)
4178+
4179+ component.is_visible = False
4180+ self.assertEqual(component.is_visible, False)
4181+
4182+ component.is_visible = True
4183+ self.assertEqual(component.is_visible, True)
4184+
4185+ def test_custom_component(self):
4186+ """Users can also add components
4187+
4188+ For whatever reason, it may be that we can't import a component
4189+ from the remote bug tracker. This gives users a way to correct
4190+ the omissions."""
4191+ custom_component = self.factory.makeBugTrackerComponent(
4192+ u'example', self.comp_group, custom=True)
4193+ self.assertTrue(custom_component != None)
4194+ self.assertEqual(custom_component.is_custom, True)
4195+
4196+ def test_multiple_component_creation(self):
4197+ """Verify several components can be created at once"""
4198+ comp_a = self.factory.makeBugTrackerComponent(
4199+ u'example-a', self.comp_group)
4200+ comp_b = self.factory.makeBugTrackerComponent(
4201+ u'example-b', self.comp_group)
4202+ comp_c = self.factory.makeBugTrackerComponent(
4203+ u'example-c', self.comp_group, True)
4204+
4205+ self.assertTrue(comp_a is not None)
4206+ self.assertTrue(comp_b is not None)
4207+ self.assertTrue(comp_c is not None)
4208+
4209+
4210+class TestBugTrackerWithComponents(TestCaseWithFactory):
4211+
4212+ layer = DatabaseFunctionalLayer
4213+
4214+ def setUp(self):
4215+ super(TestBugTrackerWithComponents, self).setUp()
4216+
4217+ regular_user = self.factory.makePerson()
4218+ login_person(regular_user)
4219+
4220+ self.bug_tracker = self.factory.makeBugTracker()
4221+
4222+ def test_empty_bugtracker(self):
4223+ """Trivial case of bugtracker with no products or components"""
4224+ self.assertTrue(self.bug_tracker is not None)
4225+
4226+ # Empty bugtrackers shouldn't return component groups
4227+ comp_group = self.bug_tracker.getRemoteComponentGroup(u'non-existant')
4228+ self.assertEqual(comp_group, None)
4229+
4230+ # Verify it contains no component groups
4231+ comp_groups = self.bug_tracker.getAllRemoteComponentGroups()
4232+ self.assertEqual(len(list(comp_groups)), 0)
4233+
4234+ def test_single_product_bugtracker(self):
4235+ """Bug tracker with a single (default) product and several components
4236+ """
4237+ # Add a component group and fill it with some components
4238+ default_comp_group = self.bug_tracker.addRemoteComponentGroup(
4239+ u'alpha')
4240+ default_comp_group.addComponent(u'example-a')
4241+ default_comp_group.addComponent(u'example-b')
4242+ default_comp_group.addComponent(u'example-c')
4243+
4244+ # Verify that retrieving an invalid component group returns nothing
4245+ comp_group = self.bug_tracker.getRemoteComponentGroup(u'non-existant')
4246+ self.assertEqual(comp_group, None)
4247+
4248+ # Now retrieve the component group we added
4249+ comp_group = self.bug_tracker.getRemoteComponentGroup(u'alpha')
4250+ self.assertEqual(comp_group, default_comp_group)
4251+ self.assertEqual(comp_group.name, u'alpha')
4252+
4253+ # Verify there is only the one component group in the tracker
4254+ comp_groups = self.bug_tracker.getAllRemoteComponentGroups()
4255+ self.assertEqual(len(list(comp_groups)), 1)
4256+
4257+ def test_multiple_product_bugtracker(self):
4258+ """Bug tracker with multiple products and components"""
4259+ # Create several component groups with varying numbers of components
4260+ comp_group_i = self.bug_tracker.addRemoteComponentGroup(u'alpha')
4261+
4262+ comp_group_ii = self.bug_tracker.addRemoteComponentGroup(u'beta')
4263+ comp_group_ii.addComponent(u'example-beta-1')
4264+
4265+ comp_group_iii = self.bug_tracker.addRemoteComponentGroup(u'gamma')
4266+ comp_group_iii.addComponent(u'example-gamma-1')
4267+ comp_group_iii.addComponent(u'example-gamma-2')
4268+ comp_group_iii.addComponent(u'example-gamma-3')
4269+
4270+ # Retrieving a non-existant component group returns nothing
4271+ comp_group = self.bug_tracker.getRemoteComponentGroup(u'non-existant')
4272+ self.assertEqual(comp_group, None)
4273+
4274+ # Now retrieve one of the real component groups
4275+ comp_group = self.bug_tracker.getRemoteComponentGroup(u'beta')
4276+ self.assertEqual(comp_group, comp_group_ii)
4277+
4278+ # Check the correct number of component groups are in the bug tracker
4279+ comp_groups = self.bug_tracker.getAllRemoteComponentGroups()
4280+ self.assertEqual(len(list(comp_groups)), 3)
4281+
4282+ def test_get_components_for_component_group(self):
4283+ """Retrieve a set of components from a given product"""
4284+ # Create a component group with some components
4285+ default_comp_group = self.bug_tracker.addRemoteComponentGroup(
4286+ u'alpha')
4287+ default_comp_group.addComponent(u'example-a')
4288+ default_comp_group.addComponent(u'example-b')
4289+ default_comp_group.addComponent(u'example-c')
4290+
4291+ # Verify group has the correct number of components
4292+ comp_group = self.bug_tracker.getRemoteComponentGroup(u'alpha')
4293+ self.assertEqual(len(list(comp_group.components)), 3)
4294+
4295+ # Check one of the components, that it is what we expect
4296+ comp = comp_group.getComponent(u'example-b')
4297+ self.assertEqual(comp.name, u'example-b')
4298+
4299+
4300+def test_suite():
4301+ suite = unittest.TestSuite()
4302+ suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
4303+
4304+ return suite
4305
4306=== modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py'
4307--- lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-09-16 12:27:46 +0000
4308+++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-10-04 22:08:24 +0000
4309@@ -1,4 +1,4 @@
4310-# Copyright 2009 Canonical Ltd. This software is licensed under the
4311+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
4312 # GNU Affero General Public License version 3 (see the file LICENSE).
4313
4314 # pylint: disable-msg=E0211,E0213
4315@@ -256,6 +256,15 @@
4316
4317 was_built = Attribute("Whether or not modified by the builddfarm.")
4318
4319+ # This doesn't belong here. It really belongs in IPackageBuild, but
4320+ # the TAL assumes it can read this directly.
4321+ dependencies = exported(
4322+ TextLine(
4323+ title=_('Dependencies'), required=False,
4324+ description=_(
4325+ 'Debian-like dependency line that must be satisfied before '
4326+ 'attempting to build this request.')))
4327+
4328
4329 class ISpecificBuildFarmJob(IBuildFarmJob):
4330 """A marker interface with which to define adapters for IBuildFarmJob.
4331
4332=== modified file 'lib/lp/buildmaster/interfaces/packagebuild.py'
4333--- lib/lp/buildmaster/interfaces/packagebuild.py 2010-09-10 12:29:36 +0000
4334+++ lib/lp/buildmaster/interfaces/packagebuild.py 2010-10-04 22:08:24 +0000
4335@@ -61,12 +61,6 @@
4336 description=_("A URL for failed upload logs."
4337 "Will be None if there was no failure.")))
4338
4339- dependencies = exported(
4340- TextLine(
4341- title=_('Dependencies'), required=False,
4342- description=_('Debian-like dependency line that must be satisfied'
4343- ' before attempting to build this request.')))
4344-
4345 build_farm_job = Reference(
4346 title=_('Build farm job'), schema=IBuildFarmJob, required=True,
4347 readonly=True, description=_('The base build farm job.'))
4348
4349=== modified file 'lib/lp/buildmaster/model/buildfarmjob.py'
4350--- lib/lp/buildmaster/model/buildfarmjob.py 2010-09-16 12:27:46 +0000
4351+++ lib/lp/buildmaster/model/buildfarmjob.py 2010-10-04 22:08:24 +0000
4352@@ -237,6 +237,8 @@
4353
4354 failure_count = Int(name='failure_count', allow_none=False)
4355
4356+ dependencies = None
4357+
4358 def __init__(self, job_type, status=BuildStatus.NEEDSBUILD,
4359 processor=None, virtualized=None, date_created=None):
4360 super(BuildFarmJob, self).__init__()
4361@@ -273,7 +275,7 @@
4362 return self.date_finished - self.date_started
4363
4364 def makeJob(self):
4365- """See `IBuildFarmJob`."""
4366+ """See `IBuildFarmJobOld`."""
4367 raise NotImplementedError
4368
4369 def jobStarted(self):
4370@@ -420,7 +422,8 @@
4371 # specific private builds to which they have access.
4372 origin.extend(left_join_archive)
4373 origin.append(LeftJoin(
4374- TeamParticipation, TeamParticipation.teamID == Archive.ownerID))
4375+ TeamParticipation,
4376+ TeamParticipation.teamID == Archive.ownerID))
4377 extra_clauses.append(
4378 Or(Coalesce(Archive.private, False) == False,
4379 TeamParticipation.person == user))
4380
4381=== modified file 'lib/lp/code/browser/branch.py'
4382--- lib/lp/code/browser/branch.py 2010-09-22 18:54:36 +0000
4383+++ lib/lp/code/browser/branch.py 2010-10-04 22:08:24 +0000
4384@@ -105,6 +105,7 @@
4385 from canonical.widgets.branch import TargetBranchWidget
4386 from canonical.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription
4387 from canonical.widgets.lazrjs import vocabulary_to_choice_edit_items
4388+from lp.app.errors import NotFoundError
4389 from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
4390 from lp.bugs.interfaces.bug import IBugSet
4391 from lp.bugs.interfaces.bugbranch import IBugBranch
4392@@ -146,6 +147,9 @@
4393 from lp.registry.interfaces.productseries import IProductSeries
4394 from lp.registry.vocabularies import UserTeamsParticipationPlusSelfVocabulary
4395 from lp.services.propertycache import cachedproperty
4396+from lp.translations.interfaces.translationtemplatesbuild import (
4397+ ITranslationTemplatesBuildSource,
4398+ )
4399
4400
4401 def quote(text):
4402@@ -238,6 +242,16 @@
4403 """Traverses to the `ICodeImport` for the branch."""
4404 return self.context.code_import
4405
4406+ @stepthrough("+translation-templates-build")
4407+ def traverse_translation_templates_build(self, id_string):
4408+ """Traverses to a `TranslationTemplatesBuild`."""
4409+ try:
4410+ buildfarmjob_id = int(id_string)
4411+ except ValueError:
4412+ raise NotFoundError(id_string)
4413+ source = getUtility(ITranslationTemplatesBuildSource)
4414+ return source.getByBuildFarmJob(buildfarmjob_id)
4415+
4416
4417 class BranchEditMenu(NavigationMenu):
4418 """Edit menu for IBranch."""
4419
4420=== modified file 'lib/lp/code/browser/branchmergeproposal.py'
4421--- lib/lp/code/browser/branchmergeproposal.py 2010-09-27 20:47:58 +0000
4422+++ lib/lp/code/browser/branchmergeproposal.py 2010-10-04 22:08:24 +0000
4423@@ -547,7 +547,7 @@
4424 return diff_text.count('\n') >= config.diff.max_format_lines
4425
4426
4427-class ICodeReviewNewRevisions(Interface):
4428+class ICodeReviewNewRevisions(IComment):
4429 """Marker interface used to register views for CodeReviewNewRevisions."""
4430
4431
4432@@ -557,7 +557,7 @@
4433 Each object instance represents a number of revisions scanned at a
4434 particular time.
4435 """
4436- implements(IComment, ICodeReviewNewRevisions)
4437+ implements(ICodeReviewNewRevisions)
4438
4439 def __init__(self, revisions, date, branch):
4440 self.revisions = revisions
4441@@ -567,6 +567,13 @@
4442 # The date attribute is used to sort the comments in the conversation.
4443 self.date = date
4444
4445+ # Other standard IComment attributes are not used.
4446+ self.extra_css_class = None
4447+ self.comment_author = None
4448+ self.body_text = None
4449+ self.comment_date = None
4450+ self.display_attachments = False
4451+
4452
4453 class CodeReviewNewRevisionsView(LaunchpadView):
4454 """The view for rendering the new revisions."""
4455
4456=== modified file 'lib/lp/code/browser/codereviewcomment.py'
4457--- lib/lp/code/browser/codereviewcomment.py 2010-10-01 14:14:04 +0000
4458+++ lib/lp/code/browser/codereviewcomment.py 2010-10-04 22:08:24 +0000
4459@@ -43,6 +43,10 @@
4460 from lp.services.propertycache import cachedproperty
4461
4462
4463+class ICodeReviewDisplayComment(IComment, ICodeReviewComment):
4464+ """Marker interface for displaying code review comments."""
4465+
4466+
4467 class CodeReviewDisplayComment:
4468 """A code review comment or activity or both.
4469
4470@@ -51,7 +55,7 @@
4471 only code in the model itself.
4472 """
4473
4474- implements(IComment)
4475+ implements(ICodeReviewDisplayComment)
4476
4477 delegates(ICodeReviewComment, 'comment')
4478
4479@@ -70,6 +74,40 @@
4480 else:
4481 return ''
4482
4483+ @cachedproperty
4484+ def comment_author(self):
4485+ """The author of the comment."""
4486+ return self.comment.message.owner
4487+
4488+ @cachedproperty
4489+ def has_body(self):
4490+ """Is there body text?"""
4491+ return bool(self.body_text)
4492+
4493+ @cachedproperty
4494+ def body_text(self):
4495+ """Get the body text for the message."""
4496+ return self.comment.message_body
4497+
4498+ @cachedproperty
4499+ def comment_date(self):
4500+ """The date of the comment."""
4501+ return self.comment.message.datecreated
4502+
4503+ @cachedproperty
4504+ def all_attachments(self):
4505+ return self.comment.getAttachments()
4506+
4507+ @cachedproperty
4508+ def display_attachments(self):
4509+ # Attachments to show.
4510+ return [DiffAttachment(alias) for alias in self.all_attachments[0]]
4511+
4512+ @cachedproperty
4513+ def other_attachments(self):
4514+ # Attachments to not show.
4515+ return self.all_attachments[1]
4516+
4517
4518 class CodeReviewCommentPrimaryContext:
4519 """The primary context is the comment is that of the source branch."""
4520@@ -132,45 +170,11 @@
4521 """The decorated code review comment."""
4522 return CodeReviewDisplayComment(self.context)
4523
4524- @cachedproperty
4525- def comment_author(self):
4526- """The author of the comment."""
4527- return self.context.message.owner
4528-
4529- @cachedproperty
4530- def has_body(self):
4531- """Is there body text?"""
4532- return bool(self.body_text)
4533-
4534- @cachedproperty
4535- def body_text(self):
4536- """Get the body text for the message."""
4537- return self.context.message_body
4538-
4539- @cachedproperty
4540- def comment_date(self):
4541- """The date of the comment."""
4542- return self.context.message.datecreated
4543-
4544 # Should the comment be shown in full?
4545 full_comment = True
4546 # Show comment expanders?
4547 show_expanders = False
4548
4549- @cachedproperty
4550- def all_attachments(self):
4551- return self.context.getAttachments()
4552-
4553- @cachedproperty
4554- def display_attachments(self):
4555- # Attachments to show.
4556- return [DiffAttachment(alias) for alias in self.all_attachments[0]]
4557-
4558- @cachedproperty
4559- def other_attachments(self):
4560- # Attachments to not show.
4561- return self.all_attachments[1]
4562-
4563
4564 class CodeReviewCommentSummary(CodeReviewCommentView):
4565 """Summary view of a CodeReviewComment"""
4566
4567=== modified file 'lib/lp/code/browser/configure.zcml'
4568--- lib/lp/code/browser/configure.zcml 2010-09-30 22:26:08 +0000
4569+++ lib/lp/code/browser/configure.zcml 2010-10-04 22:08:24 +0000
4570@@ -691,6 +691,16 @@
4571 name="+index"
4572 template="../templates/codereviewcomment-index.pt"/>
4573 <browser:page
4574+ name="+fragment"
4575+ template="../templates/codereviewcomment-fragment.pt"/>
4576+ </browser:pages>
4577+ <browser:pages
4578+ facet="branches"
4579+ for="lp.code.browser.codereviewcomment.ICodeReviewDisplayComment"
4580+ layer="lp.code.publisher.CodeLayer"
4581+ class="lp.code.browser.codereviewcomment.CodeReviewCommentView"
4582+ permission="zope.Public">
4583+ <browser:page
4584 name="+comment-header"
4585 template="../templates/codereviewcomment-header.pt"/>
4586 <browser:page
4587@@ -699,9 +709,6 @@
4588 <browser:page
4589 name="+comment-footer"
4590 template="../templates/codereviewcomment-footer.pt"/>
4591- <browser:page
4592- name="+fragment"
4593- template="../templates/codereviewcomment-fragment.pt"/>
4594 </browser:pages>
4595 <browser:pages
4596 facet="branches"
4597
4598=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
4599--- lib/lp/code/browser/tests/test_branchmergeproposal.py 2010-09-15 20:41:46 +0000
4600+++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2010-10-04 22:08:24 +0000
4601@@ -23,6 +23,7 @@
4602 from canonical.launchpad.database.message import MessageSet
4603 from canonical.launchpad.webapp.interfaces import IPrimaryContext
4604 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
4605+from canonical.launchpad.webapp.testing import verifyObject
4606 from canonical.testing import (
4607 DatabaseFunctionalLayer,
4608 LaunchpadFunctionalLayer,
4609@@ -35,8 +36,10 @@
4610 BranchMergeProposalMergedView,
4611 BranchMergeProposalVoteView,
4612 DecoratedCodeReviewVoteReference,
4613+ ICodeReviewNewRevisions,
4614 latest_proposals_for_each_branch,
4615 )
4616+from lp.code.browser.codereviewcomment import CodeReviewDisplayComment
4617 from lp.code.enums import (
4618 BranchMergeProposalStatus,
4619 CodeReviewVote,
4620@@ -45,6 +48,7 @@
4621 PreviewDiff,
4622 StaticDiff,
4623 )
4624+from lp.code.tests.helpers import add_revision_to_branch
4625 from lp.testing import (
4626 login_person,
4627 TestCaseWithFactory,
4628@@ -575,6 +579,20 @@
4629 view = create_initialized_view(self.bmp, '+index')
4630 self.assertEqual([], view.linked_bugs)
4631
4632+ def test_CodeReviewNewRevisions_implements_ICodeReviewNewRevisions(self):
4633+ # The browser helper class implements its interface.
4634+ review_date = datetime(2009, 9, 10, tzinfo=pytz.UTC)
4635+ revision_date = review_date + timedelta(days=1)
4636+ bmp = self.factory.makeBranchMergeProposal(
4637+ date_created=review_date)
4638+ revision = add_revision_to_branch(
4639+ self.factory, bmp.source_branch, revision_date)
4640+
4641+ view = create_initialized_view(bmp, '+index')
4642+ new_revisions = view.conversation.comments[0]
4643+
4644+ self.assertTrue(verifyObject(ICodeReviewNewRevisions, new_revisions))
4645+
4646 def test_include_superseded_comments(self):
4647 for x, time in zip(range(3), time_counter()):
4648 if x != 0:
4649@@ -707,7 +725,8 @@
4650 body='testing',
4651 attachments=[('test.diff', 'text/plain', attachment_body)])
4652 message = MessageSet().fromEmail(msg.as_string())
4653- return bmp.createCommentFromMessage(message, None, None, msg)
4654+ return CodeReviewDisplayComment(
4655+ bmp.createCommentFromMessage(message, None, None, msg))
4656
4657 def test_nonascii_in_attachment_renders(self):
4658 # The view should render without errors.
4659@@ -723,7 +742,7 @@
4660 # Need to commit in order to read the diff out of the librarian.
4661 transaction.commit()
4662 view = create_initialized_view(comment, '+comment-body')
4663- [diff_attachment] = view.display_attachments
4664+ [diff_attachment] = view.comment.display_attachments
4665 self.assertEqual(u'\u2615', diff_attachment.diff_text)
4666
4667
4668
4669=== modified file 'lib/lp/code/browser/tests/test_codereviewcomment.py'
4670--- lib/lp/code/browser/tests/test_codereviewcomment.py 2010-08-20 20:31:18 +0000
4671+++ lib/lp/code/browser/tests/test_codereviewcomment.py 2010-10-04 22:08:24 +0000
4672@@ -3,35 +3,52 @@
4673
4674 """Unit tests for CodeReviewComments."""
4675
4676+from __future__ import with_statement
4677+
4678 __metaclass__ = type
4679
4680 import unittest
4681
4682 from canonical.launchpad.webapp.interfaces import IPrimaryContext
4683+from canonical.launchpad.webapp.testing import verifyObject
4684 from canonical.testing import DatabaseFunctionalLayer
4685+from lp.code.browser.codereviewcomment import (
4686+ CodeReviewDisplayComment,
4687+ ICodeReviewDisplayComment,
4688+ )
4689 from lp.testing import (
4690- login_person,
4691+ person_logged_in,
4692 TestCaseWithFactory,
4693 )
4694
4695
4696-class TestCodeReviewCommentPrimaryContext(TestCaseWithFactory):
4697- # Tests the adaptation of a code review comment into a primary context.
4698+class TestCodeReviewComments(TestCaseWithFactory):
4699
4700 layer = DatabaseFunctionalLayer
4701
4702 def testPrimaryContext(self):
4703+ # Tests the adaptation of a code review comment into a primary
4704+ # context.
4705 # We need a person to make a comment.
4706- commenter = self.factory.makePerson()
4707- login_person(commenter)
4708- # The primary context of a code review comment is the same as the
4709- # primary context for the branch merge proposal that the comment is
4710- # for.
4711- comment = self.factory.makeCodeReviewComment()
4712+ with person_logged_in(self.factory.makePerson()):
4713+ # The primary context of a code review comment is the same
4714+ # as the primary context for the branch merge proposal that
4715+ # the comment is for.
4716+ comment = self.factory.makeCodeReviewComment()
4717+
4718 self.assertEqual(
4719 IPrimaryContext(comment).context,
4720 IPrimaryContext(comment.branch_merge_proposal).context)
4721
4722+ def test_display_comment_provides_icodereviewdisplaycomment(self):
4723+ # The CodeReviewDisplayComment class provides IComment.
4724+ with person_logged_in(self.factory.makePerson()):
4725+ comment = self.factory.makeCodeReviewComment()
4726+
4727+ display_comment = CodeReviewDisplayComment(comment)
4728+
4729+ verifyObject(ICodeReviewDisplayComment, display_comment)
4730+
4731
4732 def test_suite():
4733 return unittest.TestLoader().loadTestsFromName(__name__)
4734
4735=== modified file 'lib/lp/code/doc/branch.txt'
4736--- lib/lp/code/doc/branch.txt 2010-10-01 14:14:04 +0000
4737+++ lib/lp/code/doc/branch.txt 2010-10-04 22:08:24 +0000
4738@@ -405,6 +405,7 @@
4739 sourcepackagerecipedata.base_branch
4740 sourcepackagerecipedatainstruction.branch
4741 specificationbranch.branch
4742+ translationtemplatesbuild.branch
4743
4744 (Unfortunately, references can form a cycle-- note that
4745 codereviewcomments
4746
4747=== modified file 'lib/lp/code/interfaces/branchrevision.py'
4748--- lib/lp/code/interfaces/branchrevision.py 2010-09-09 01:04:26 +0000
4749+++ lib/lp/code/interfaces/branchrevision.py 2010-10-04 22:08:24 +0000
4750@@ -26,8 +26,6 @@
4751 ancestry of a branch. History revisions have an integer sequence, merged
4752 revisions have sequence set to None.
4753 """
4754- id = Int(title=_('The database revision ID'))
4755-
4756 sequence = Int(
4757 title=_("Revision number"), required=True,
4758 description=_("The index of the revision within the branch's history."
4759
4760=== modified file 'lib/lp/code/model/branch.py'
4761--- lib/lp/code/model/branch.py 2010-09-21 02:46:02 +0000
4762+++ lib/lp/code/model/branch.py 2010-10-04 22:08:24 +0000
4763@@ -919,7 +919,7 @@
4764 def getScannerData(self):
4765 """See `IBranch`."""
4766 columns = (
4767- BranchRevision.id, BranchRevision.sequence, Revision.revision_id)
4768+ BranchRevision.sequence, Revision.revision_id)
4769 rows = Store.of(self).using(Revision, BranchRevision).find(
4770 columns,
4771 Revision.id == BranchRevision.revision_id,
4772@@ -927,7 +927,7 @@
4773 rows = rows.order_by(BranchRevision.sequence)
4774 ancestry = set()
4775 history = []
4776- for branch_revision_id, sequence, revision_id in rows:
4777+ for sequence, revision_id in rows:
4778 ancestry.add(revision_id)
4779 if sequence is not None:
4780 history.append(revision_id)
4781@@ -1027,10 +1027,14 @@
4782 """Delete jobs for this branch prior to deleting branch.
4783
4784 This deletion includes `BranchJob`s associated with the branch,
4785- as well as `BuildQueue` entries for `TranslationTemplateBuildJob`s.
4786+ as well as `BuildQueue` entries for `TranslationTemplateBuildJob`s
4787+ and `TranslationTemplateBuild`s.
4788 """
4789 # Avoid circular imports.
4790 from lp.code.model.branchjob import BranchJob
4791+ from lp.translations.model.translationtemplatesbuild import (
4792+ TranslationTemplatesBuild,
4793+ )
4794
4795 store = Store.of(self)
4796 affected_jobs = Select(
4797@@ -1044,6 +1048,10 @@
4798 # Delete Jobs. Their BranchJobs cascade along in the database.
4799 store.find(Job, Job.id.is_in(affected_jobs)).remove()
4800
4801+ store.find(
4802+ TranslationTemplatesBuild,
4803+ TranslationTemplatesBuild.branch == self).remove()
4804+
4805 def destroySelf(self, break_references=False):
4806 """See `IBranch`."""
4807 from lp.code.interfaces.branchjob import IReclaimBranchSpaceJobSource
4808
4809=== modified file 'lib/lp/code/model/branchmergeproposal.py'
4810--- lib/lp/code/model/branchmergeproposal.py 2010-09-22 18:55:25 +0000
4811+++ lib/lp/code/model/branchmergeproposal.py 2010-10-04 22:08:24 +0000
4812@@ -633,7 +633,7 @@
4813 SourceRevision,
4814 SourceRevision.branch_id == self.source_branch.id,
4815 SourceRevision.sequence != None,
4816- TargetRevision.id == None)
4817+ TargetRevision.branch_id == None)
4818 return result.order_by(Desc(SourceRevision.sequence)).config(limit=10)
4819
4820 def createComment(self, owner, subject, content=None, vote=None,
4821
4822=== modified file 'lib/lp/code/model/branchrevision.py'
4823--- lib/lp/code/model/branchrevision.py 2010-09-09 01:04:26 +0000
4824+++ lib/lp/code/model/branchrevision.py 2010-10-04 22:08:24 +0000
4825@@ -21,8 +21,7 @@
4826 class BranchRevision(Storm):
4827 """See `IBranchRevision`."""
4828 __storm_table__ = 'BranchRevision'
4829-
4830- id = Int(primary=True)
4831+ __storm_primary__ = "branch_id", "revision_id"
4832
4833 implements(IBranchRevision)
4834
4835
4836=== modified file 'lib/lp/code/model/tests/test_branchjob.py'
4837--- lib/lp/code/model/tests/test_branchjob.py 2010-08-20 20:31:18 +0000
4838+++ lib/lp/code/model/tests/test_branchjob.py 2010-10-04 22:08:24 +0000
4839@@ -518,9 +518,7 @@
4840 except bzr_errors.NoSuchRevision:
4841 revno = None
4842 if existing is not None:
4843- branchrevision = IMasterStore(branch).find(
4844- BranchRevision, BranchRevision.id == existing.id)
4845- branchrevision.remove()
4846+ branch.removeBranchRevisions([existing.revision.revision_id])
4847 branch.createBranchRevision(revno, revision)
4848
4849 def create3CommitsBranch(self):
4850
4851=== modified file 'lib/lp/code/templates/codereviewcomment-body.pt'
4852--- lib/lp/code/templates/codereviewcomment-body.pt 2010-04-16 04:20:43 +0000
4853+++ lib/lp/code/templates/codereviewcomment-body.pt 2010-10-04 22:08:24 +0000
4854@@ -3,9 +3,9 @@
4855 xmlns:metal="http://xml.zope.org/namespaces/metal"
4856 omit-tag="">
4857
4858- <tal:message replace="structure view/body_text/fmt:obfuscate-email/fmt:nice_pre" />
4859+ <tal:message replace="structure view/comment/body_text/fmt:obfuscate-email/fmt:nice_pre" />
4860
4861- <tal:good-attachments repeat="attachment view/display_attachments">
4862+ <tal:good-attachments repeat="attachment view/comment/display_attachments">
4863 <div class="boardComment attachment">
4864 <div class="boardCommentDetails filename"><a tal:content="attachment/filename" tal:attributes="href attachment/getURL"/></div>
4865 <div class="boardCommentBody attachmentBody" tal:content="structure attachment/diff_text/fmt:diff"/>
4866
4867=== modified file 'lib/lp/code/templates/codereviewcomment-header.pt'
4868--- lib/lp/code/templates/codereviewcomment-header.pt 2010-02-18 16:40:09 +0000
4869+++ lib/lp/code/templates/codereviewcomment-header.pt 2010-10-04 22:08:24 +0000
4870@@ -3,9 +3,9 @@
4871 xmlns:metal="http://xml.zope.org/namespaces/metal"
4872 omit-tag="">
4873
4874- <tal:author replace="structure view/comment_author/fmt:link:mainsite"/>
4875- <tal:has-body condition="view/has_body">wrote</tal:has-body>
4876- <tal:date replace="view/comment_date/fmt:displaydate" />
4877+ <tal:author replace="structure context/comment_author/fmt:link:mainsite"/>
4878+ <tal:has-body condition="context/has_body">wrote</tal:has-body>
4879+ <tal:date replace="context/comment_date/fmt:displaydate" />
4880 <span tal:condition="context/from_superseded" class="sprite warning-icon"
4881 style="float: right">Posted in <a
4882 tal:attributes="href context/branch_merge_proposal/fmt:url">a
4883
4884=== modified file 'lib/lp/codehosting/sshserver/session.py'
4885--- lib/lp/codehosting/sshserver/session.py 2010-08-20 20:31:18 +0000
4886+++ lib/lp/codehosting/sshserver/session.py 2010-10-04 22:08:24 +0000
4887@@ -9,9 +9,20 @@
4888 ]
4889
4890 import os
4891+import signal
4892+import socket
4893 import urlparse
4894
4895-from twisted.internet.process import ProcessExitedAlready
4896+from zope.event import notify
4897+from zope.interface import implements
4898+
4899+from twisted.internet import (
4900+ error,
4901+ fdesc,
4902+ interfaces,
4903+ main,
4904+ process,
4905+ )
4906 from twisted.python import log
4907 from zope.event import notify
4908
4909@@ -35,6 +46,229 @@
4910 """Raised when a session is asked to execute a forbidden command."""
4911
4912
4913+class _WaitForExit(process.ProcessReader):
4914+ """Wait on a socket for the exit status."""
4915+
4916+ def __init__(self, reactor, proc, sock):
4917+ super(_WaitForExit, self).__init__(reactor, proc, 'exit',
4918+ sock.fileno())
4919+ self._sock = sock
4920+ self.connected = 1
4921+
4922+ def close(self):
4923+ self._sock.close()
4924+
4925+ def dataReceived(self, data):
4926+ # TODO: how do we handle getting only *some* of the content?, Maybe we
4927+ # need to read more bytes first...
4928+
4929+ # This is the only thing we do differently from the standard
4930+ # ProcessReader. When we get data on this socket, we need to treat it
4931+ # as a return code, or a failure.
4932+ if not data.startswith('exited'):
4933+ # Bad data, we want to signal that we are closing the connection
4934+ # TODO: How?
4935+ # self.proc.?
4936+ self.close()
4937+ # I don't know what to put here if we get bogus data, but I *do*
4938+ # want to say that the process is now considered dead to me
4939+ log.err('Got invalid exit information: %r' % (data,))
4940+ exit_status = (255 << 8)
4941+ else:
4942+ exit_status = int(data.split('\n')[1])
4943+ self.proc.processEnded(exit_status)
4944+
4945+
4946+class ForkedProcessTransport(process.BaseProcess):
4947+ """Wrap the forked process in a ProcessTransport so we can talk to it.
4948+
4949+ Note that instantiating the class creates the fork and sets it up in the
4950+ reactor.
4951+ """
4952+
4953+ implements(interfaces.IProcessTransport)
4954+
4955+ # Design decisions
4956+ # [Decision #a]
4957+ # Inherit from process.BaseProcess
4958+ # This seems slightly risky, as process.BaseProcess is actually
4959+ # imported from twisted.internet._baseprocess.BaseProcess. The
4960+ # real-world Process then actually inherits from process._BaseProcess
4961+ # I've also had to copy a fair amount from the actual Process
4962+ # command.
4963+ # One option would be to inherit from process.Process, and just
4964+ # override stuff like __init__ and reapProcess which I don't want to
4965+ # do in the same way. (Is it ok not to call your Base classes
4966+ # __init__ if you don't want to do that exact work?)
4967+
4968+ def __init__(self, reactor, executable, args, environment, proto):
4969+ process.BaseProcess.__init__(self, proto)
4970+ # Map from standard file descriptor to the associated pipe
4971+ self.pipes = {}
4972+ pid, path, sock = self._spawn(executable, args, environment)
4973+ self._fifo_path = path
4974+ self.pid = pid
4975+ self.process_sock = sock
4976+ self._fifo_path = path
4977+ self._connectSpawnToReactor(reactor)
4978+ if self.proto is not None:
4979+ self.proto.makeConnection(self)
4980+
4981+ def _sendMessageToService(self, message):
4982+ """Send a message to the Forking service and get the response"""
4983+ path = config.codehosting.forking_daemon_socket
4984+ client_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
4985+ log.msg('Connecting to Forking Service @ socket: %s for %r'
4986+ % (path, message))
4987+ try:
4988+ client_sock.connect(path)
4989+ client_sock.sendall(message)
4990+ # We define the requests to be no bigger than 1kB. (For now)
4991+ response = client_sock.recv(1024)
4992+ except socket.error, e:
4993+ # TODO: What exceptions should be raised?
4994+ # Raising the raw exception seems to kill the twisted reactor
4995+ # Note that if the connection is refused, we *could* just
4996+ # fall back on a regular 'spawnProcess' call.
4997+ log.err('Connection failed: %s' % (e,))
4998+ raise
4999+ if response.startswith("FAILURE"):
5000+ raise RuntimeError('Failed to send message: %r' % (response,))
The diff has been truncated for viewing.