/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.152.6 by Vincent Ladeuil
Really use the transports and test against all targeted protocols.
1
# Copyright (C) 2008 Canonical Ltd
0.152.1 by Vincent Ladeuil
Empty shell
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
0.152.6 by Vincent Ladeuil
Really use the transports and test against all targeted protocols.
17
"""Upload a working tree, incrementally.
18
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
19
The only bzr-related info uploaded with the working tree is the corresponding
0.152.6 by Vincent Ladeuil
Really use the transports and test against all targeted protocols.
20
revision id. The uploaded working tree is not linked to any other bzr data.
21
0.152.35 by Martin Albisetti
* Tell the user if the remote location is already up to date
22
The intended use is for web developers which keep their web sites versioned
23
with bzr, can can use either FTP or SFTP to upload their site.
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
24
25
Known limitations:
26
- Symlinks are ignored,
27
28
- chmod bits are not supported.
0.152.6 by Vincent Ladeuil
Really use the transports and test against all targeted protocols.
29
"""
0.152.1 by Vincent Ladeuil
Empty shell
30
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
31
# TODO: the chmod bits *can* be supported via the upload protocols
32
# (i.e. poorly), but since the web developers use these protocols to upload
33
# manually, it is expected that the associated web server is coherent with
34
# their presence/absence. In other words, if a web hosting provider requires
35
# chmod bits but don't provide an ftp server that support them, well, better
36
# find another provider ;-)
37
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
38
# TODO: The message emitted in verbose mode displays local paths. That may be
39
# scary for the user when we say 'Deleting <path>' and are referring to
40
# remote files...
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
41
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
42
from bzrlib import (
0.155.2 by James Westby
Add a post_change_branch_tip hook to upload.
43
    branch,
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
44
    commands,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
45
    lazy_import,
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
46
    option,
47
    )
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
48
lazy_import.lazy_import(globals(), """
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
49
import stat
50
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
51
from bzrlib import (
0.158.1 by Gary van der Merwe
Don't require a working tree.
52
    bzrdir,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
53
    errors,
54
    revisionspec,
55
    transport,
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
56
    osutils,
0.152.40 by Martin Albisetti
We need to import urlutils if we are going to use it
57
    urlutils,
0.152.27 by Martin Albisetti
* Added error message if the working tree has uncommited changes
58
    workingtree,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
59
    )
60
""")
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
61
0.152.52 by Vincent Ladeuil
Fix bug #270219 by handling content changes during renames.
62
version_info = (0, 1, 1)
0.152.23 by Martin Albisetti
Added version_info and plugin_name
63
plugin_name = 'upload'
64
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
65
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
66
def _get_branch_option(branch, option):
67
    return branch.get_config().get_user_option(option)
68
69
def _set_branch_option(branch, option, value):
70
    branch.get_config().set_user_option(option, value)
71
72
def get_upload_location(branch):
73
    return _get_branch_option(branch, 'upload_location')
74
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
75
def set_upload_location(branch, location):
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
76
    _set_branch_option(branch, 'upload_location', location)
77
78
def get_upload_auto(branch):
79
    result = _get_branch_option(branch, 'upload_auto')
80
    # FIXME: is there a better way to do this with bzr's config API?
0.155.4 by James Westby
Add the groundwork for --auto that enables the hook for a branch.
81
    if result is not None and result.strip() == "True":
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
82
        return True
83
    return False
84
85
def set_upload_auto(branch, auto):
86
    if auto:
87
        auto_str = "True"
88
    else:
89
        auto_str = "False"
90
    _set_branch_option(branch, 'upload_auto', auto_str)
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
91
92
93
class BzrUploader(object):
94
95
    def __init__(self, branch, to_transport, outf, tree, rev_id,
96
            quiet=False):
97
        self.branch = branch
98
        self.to_transport = to_transport
99
        self.outf = outf
100
        self.tree = tree
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
101
        self.rev_id = rev_id
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
102
        self.quiet = quiet
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
103
        self._pending_deletions = []
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
104
        self._pending_renames = []
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
105
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
106
    bzr_upload_revid_file_name = '.bzr-upload.revid'
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
107
108
    def set_uploaded_revid(self, rev_id):
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
109
        # XXX: Add tests for concurrent updates, etc.
110
        self.to_transport.put_bytes(self.bzr_upload_revid_file_name, rev_id)
111
112
    def get_uploaded_revid(self):
113
        return self.to_transport.get_bytes(self.bzr_upload_revid_file_name)
0.152.7 by Vincent Ladeuil
Slight refactoring.
114
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
115
    def upload_file(self, relpath, id, mode=None):
116
        if mode is None:
117
            if self.tree.is_executable(id):
118
                mode = 0775
119
            else:
120
                mode = 0664
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
121
        if not self.quiet:
122
            self.outf.write('Uploading %s\n' % relpath)
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
123
        self.to_transport.put_bytes(relpath, self.tree.get_file_text(id), mode)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
124
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
125
    def upload_file_robustly(self, relpath, id, mode=None):
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
126
        """Upload a file, clearing the way on the remote side.
127
128
        When doing a full upload, it may happen that a directory exists where
129
        we want to put our file.
130
        """
131
        try:
132
            st = self.to_transport.stat(relpath)
133
            if stat.S_ISDIR(st.st_mode):
134
                # A simple rmdir may not be enough
135
                if not self.quiet:
136
                    self.outf.write('Clearing %s/%s\n' % (
137
                            self.to_transport.external_url(), relpath))
138
                self.to_transport.delete_tree(relpath)
139
        except errors.PathError:
140
            pass
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
141
        self.upload_file(relpath, id, mode)
142
143
    def make_remote_dir(self, relpath, mode=None):
144
        if mode is None:
145
            mode = 0775
146
        self.to_transport.mkdir(relpath, mode)
147
148
    def make_remote_dir_robustly(self, relpath, mode=None):
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
149
        """Create a remote directory, clearing the way on the remote side.
150
151
        When doing a full upload, it may happen that a file exists where we
152
        want to create our directory.
153
        """
154
        try:
155
            st = self.to_transport.stat(relpath)
156
            if not stat.S_ISDIR(st.st_mode):
157
                if not self.quiet:
158
                    self.outf.write('Deleting %s/%s\n' % (
159
                            self.to_transport.external_url(), relpath))
160
                self.to_transport.delete(relpath)
0.152.47 by Vincent Ladeuil
Don't fail a full upload on an already existing dir.
161
            else:
162
                # Ok the remote dir already exists, nothing to do
163
                return
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
164
        except errors.PathError:
165
            pass
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
166
        self.make_remote_dir(relpath, mode)
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
167
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
168
    def delete_remote_file(self, relpath):
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
169
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
170
            self.outf.write('Deleting %s\n' % relpath)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
171
        self.to_transport.delete(relpath)
172
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
173
    def delete_remote_dir(self, relpath):
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
174
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
175
            self.outf.write('Deleting %s\n' % relpath)
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
176
        self.to_transport.rmdir(relpath)
177
178
    def delete_remote_dir_maybe(self, relpath):
179
        """Try to delete relpath, keeping failures to retry later."""
180
        try:
181
            self.to_transport.rmdir(relpath)
182
        # any kind of PathError would be OK, though we normally expect
183
        # DirectoryNotEmpty
184
        except errors.PathError:
185
            self._pending_deletions.append(relpath)
186
187
    def finish_deletions(self):
188
        if self._pending_deletions:
189
            # Process the previously failed deletions in reverse order to
190
            # delete children before parents
191
            for relpath in reversed(self._pending_deletions):
192
                self.to_transport.rmdir(relpath)
193
            # The following shouldn't be needed since we use it once per
194
            # upload, but better safe than sorry ;-)
195
            self._pending_deletions = []
196
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
197
    def rename_remote(self, old_relpath, new_relpath):
198
        """Rename a remote file or directory taking care of collisions.
199
200
        To avoid collisions during bulk renames, each renamed target is
201
        temporarily assigned a unique name. When all renames have been done,
202
        each target get its proper name.
203
        """
204
        # We generate a sufficiently random name to *assume* that
205
        # no collisions will occur and don't worry about it (nor
206
        # handle it).
207
        import os
208
        import random
209
        import time
210
211
        stamp = '.tmp.%.9f.%d.%d' % (time.time(),
212
                                     os.getpid(),
213
                                     random.randint(0,0x7FFFFFFF))
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
214
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
215
            self.outf.write('Renaming %s to %s\n' % (old_relpath, new_relpath))
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
216
        self.to_transport.rename(old_relpath, stamp)
217
        self._pending_renames.append((stamp, new_relpath))
218
219
    def finish_renames(self):
220
        for (stamp, new_path) in self._pending_renames:
221
            self.to_transport.rename(stamp, new_path)
222
        # The following shouldn't be needed since we use it once per upload,
223
        # but better safe than sorry ;-)
224
        self._pending_renames = []
225
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
226
    def upload_full_tree(self):
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
227
        self.to_transport.ensure_base() # XXX: Handle errors (add
228
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
229
        self.tree.lock_read()
230
        try:
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
231
            for relpath, ie in self.tree.inventory.iter_entries():
232
                if relpath in ('', '.bzrignore'):
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
233
                    # skip root ('')
234
                    # .bzrignore has no meaning outside of a working tree
235
                    # so do not upload it
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
236
                    continue
0.152.8 by Vincent Ladeuil
Handle uploading directories.
237
                if ie.kind == 'file':
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
238
                    self.upload_file_robustly(relpath, ie.file_id)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
239
                elif ie.kind == 'directory':
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
240
                    self.make_remote_dir_robustly(relpath)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
241
                else:
242
                    raise NotImplementedError
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
243
            self.set_uploaded_revid(self.rev_id)
244
        finally:
245
            self.tree.unlock()
246
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
247
    def upload_tree(self):
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
248
        # If we can't find the revid file on the remote location, upload the
249
        # full tree instead
250
        try:
251
            rev_id = self.get_uploaded_revid()
252
        except errors.NoSuchFile:
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
253
            if not self.quiet:
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
254
                self.outf.write('No uploaded revision id found,'
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
255
                                ' switching to full upload\n')
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
256
            self.upload_full_tree()
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
257
            # We're done
258
            return
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
259
0.152.35 by Martin Albisetti
* Tell the user if the remote location is already up to date
260
        # Check if the revision hasn't already been uploaded
261
        if rev_id == self.rev_id:
262
            if not self.quiet:
263
                self.outf.write('Remote location already up to date\n')
264
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
265
        # XXX: errors out if rev_id not in branch history (probably someone
266
        # uploaded from a different branch).
267
        from_tree = self.branch.repository.revision_tree(rev_id)
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
268
        self.to_transport.ensure_base() # XXX: Handle errors (add
269
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
270
        changes = self.tree.changes_from(from_tree)
271
        self.tree.lock_read()
272
        try:
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
273
            for (path, id, kind) in changes.removed:
274
                if kind is 'file':
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
275
                    self.delete_remote_file(path)
276
                elif kind is  'directory':
277
                    self.delete_remote_dir_maybe(path)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
278
                else:
279
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
280
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
281
            for (old_path, new_path, id, kind,
282
                 content_change, exec_change) in changes.renamed:
0.152.52 by Vincent Ladeuil
Fix bug #270219 by handling content changes during renames.
283
                if content_change:
284
                    # We update the old_path content because renames and
285
                    # deletions are differed.
286
                    self.upload_file(old_path, id)
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
287
                self.rename_remote(old_path, new_path)
288
            self.finish_renames()
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
289
            self.finish_deletions()
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
290
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
291
            for (path, id, old_kind, new_kind) in changes.kind_changed:
292
                if old_kind is 'file':
293
                    self.delete_remote_file(path)
294
                elif old_kind is  'directory':
295
                    self.delete_remote_dir(path)
296
                else:
297
                    raise NotImplementedError
298
299
                if new_kind is 'file':
300
                    self.upload_file(path, id)
301
                elif new_kind is 'directory':
302
                    self.make_remote_dir(path)
303
                else:
304
                    raise NotImplementedError
305
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
306
            for (path, id, kind) in changes.added:
307
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
308
                    self.upload_file(path, id)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
309
                elif kind is 'directory':
310
                    self.make_remote_dir(path)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
311
                else:
312
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
313
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
314
            # XXX: Add a test for exec_change
315
            for (path, id, kind,
316
                 content_change, exec_change) in changes.modified:
317
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
318
                    self.upload_file(path, id)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
319
                else:
320
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
321
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
322
            self.set_uploaded_revid(self.rev_id)
323
        finally:
324
            self.tree.unlock()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
325
0.152.1 by Vincent Ladeuil
Empty shell
326
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
327
class cmd_upload(commands.Command):
328
    """Upload a working tree, as a whole or incrementally.
329
330
    If no destination is specified use the last one used.
331
    If no revision is specified upload the changes since the last upload.
332
    """
333
    takes_args = ['location?']
334
    takes_options = [
335
        'revision',
336
        'remember',
337
        option.Option('full', 'Upload the full working tree.'),
338
        option.Option('quiet', 'Do not output what is being done.',
339
                       short_name='q'),
340
        option.Option('directory',
341
                      help='Branch to upload from, '
342
                      'rather than the one containing the working directory.',
343
                      short_name='d',
344
                      type=unicode,
345
                      ),
0.155.5 by James Westby
Hook up the --upload option and document it.
346
        option.Option('auto',
347
                      'Trigger an upload from this branch whenever the tip '
348
                      'revision changes.')
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
349
       ]
350
351
    def run(self, location=None, full=False, revision=None, remember=None,
0.155.4 by James Westby
Add the groundwork for --auto that enables the hook for a branch.
352
            directory=None, quiet=False, auto=None
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
353
            ):
354
        if directory is None:
355
            directory = u'.'
0.158.1 by Gary van der Merwe
Don't require a working tree.
356
        
357
        wt, branch, relpath = \
358
            bzrdir.BzrDir.open_containing_tree_or_branch(directory)
359
        
360
        if wt:
361
            changes = wt.changes_from(wt.basis_tree())
362
    
363
            if revision is None and  changes.has_changed():
364
                raise errors.UncommittedChanges(wt)
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
365
366
        if location is None:
367
            stored_loc = get_upload_location(branch)
368
            if stored_loc is None:
369
                raise errors.BzrCommandError('No upload location'
370
                                             ' known or specified.')
371
            else:
372
                # FIXME: Not currently tested
373
                display_url = urlutils.unescape_for_display(stored_loc,
374
                        self.outf.encoding)
375
                self.outf.write("Using saved location: %s\n" % display_url)
376
                location = stored_loc
377
378
        to_transport = transport.get_transport(location)
379
        if revision is None:
380
            rev_id = branch.last_revision()
381
        else:
382
            if len(revision) != 1:
383
                raise errors.BzrCommandError(
384
                    'bzr upload --revision takes exactly 1 argument')
385
            rev_id = revision[0].in_history(branch).rev_id
386
387
        tree = branch.repository.revision_tree(rev_id)
388
389
        uploader = BzrUploader(branch, to_transport, self.outf, tree,
390
                rev_id, quiet=quiet)
391
392
        if full:
393
            uploader.upload_full_tree()
394
        else:
395
            uploader.upload_tree()
396
397
        # We uploaded successfully, remember it
398
        if get_upload_location(branch) is None or remember:
399
            set_upload_location(branch, to_transport.base)
0.155.4 by James Westby
Add the groundwork for --auto that enables the hook for a branch.
400
        if auto is not None:
401
            set_upload_auto(branch, auto)
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
402
403
0.152.1 by Vincent Ladeuil
Empty shell
404
commands.register_command(cmd_upload)
405
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
406
0.155.2 by James Westby
Add a post_change_branch_tip hook to upload.
407
from bzrlib.plugins.upload.auto_upload_hook import auto_upload_hook
408
409
branch.Branch.hooks.install_named_hook('post_change_branch_tip',
410
        auto_upload_hook,
411
        'Auto upload code from a branch when it is changed.')
412
413
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
414
def load_tests(basic_tests, module, loader):
0.153.1 by Vincent Ladeuil
Clean up references to verbose.
415
    # This module shouldn't define any tests but I don't know how to report
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
416
    # that. I prefer to update basic_tests with the other tests to detect
417
    # unwanted tests and I think that's sufficient.
0.152.1 by Vincent Ladeuil
Empty shell
418
419
    testmod_names = [
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
420
        'tests',
0.152.1 by Vincent Ladeuil
Empty shell
421
        ]
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
422
    basic_tests.addTest(loader.loadTestsFromModuleNames(
0.152.1 by Vincent Ladeuil
Empty shell
423
            ["%s.%s" % (__name__, tmn) for tmn in testmod_names]))
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
424
    return basic_tests