/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.152.72 by Vincent Ladeuil
Avoid the recursive loading to be able to BZR_PLUGINS_AT.
1
# Copyright (C) 2008, 2009, 2010 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.63 by Vincent Ladeuil
Make the doc more easily discoverable.
19
Quickstart
20
----------
21
22
To get started, it's as simple as running::
23
24
    bzr upload sftp://user@host/location/on/webserver
25
26
This will initially upload the whole working tree, and leave a file on the
27
remote location indicating the last revision that was uploaded
28
(.bzr-upload.revid), in order to avoid uploading unnecessary information the
29
next time.
30
31
If you would like to upload a specific revision, you just do:
32
33
    bzr upload -r X  sftp://user@host/location/on/webserver
34
35
bzr-upload, just as bzr does, will remember the location where you upload the 
36
first time, so you don't need to specify it every time.
37
38
If you need to re-upload the whole working tree for some reason, you can:
39
40
    bzr upload --full sftp://user@host/location/on/webserver
41
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
42
This command only works on the revision beening uploaded is a decendent of the
43
revision that was previously uploaded, and that they are hence from branches
44
that have not diverged. Branches are considered diverged if the destination
45
branch's most recent commit is one that has not been merged (directly or
46
indirectly) by the source branch.
47
48
If branches have diverged, you can use 'bzr upload --overwrite' to replace
49
the other branch completely, discarding its unmerged changes.
50
0.152.63 by Vincent Ladeuil
Make the doc more easily discoverable.
51
52
Automatically Uploading
53
-----------------------
54
55
bzr-upload comes with a hook that can be used to trigger an upload whenever
56
the tip of the branch changes, including on commit, push, uncommit etc. This
57
would allow you to keep the code on the target up to date automatically.
58
59
The easiest way to enable this is to run upload with the --auto option.
60
61
     bzr upload --auto
62
63
will enable the hook for this branch. If you were to do a commit in this branch
64
now you would see it trigger the upload automatically.
65
66
If you wish to disable this for a branch again then you can use the --no-auto
67
option.
68
69
     bzr upload --no-auto
70
71
will disable the feature for that branch.
72
73
Since the auto hook is triggered automatically, you can't use the --quiet
74
option available for the upload command. Instead, you can set the
75
'upload_auto_quiet' configuration variable to True or False in either
76
bazaar.conf, locations.conf or branch.conf.
77
78
79
Storing the '.bzr-upload.revid' file
80
------------------------------------
81
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
82
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.
83
revision id. The uploaded working tree is not linked to any other bzr data.
84
0.152.63 by Vincent Ladeuil
Make the doc more easily discoverable.
85
If the layout of your remote server is such that you can't write in the
86
root directory but only in the directories inside that root, you will need
87
to use the 'upload_revid_location' configuration variable to specify the
88
relative path to be used. That configuration variable can be specified in
89
locations.conf or branch.conf.
90
91
For example, given the following layout:
92
93
  Project/
94
    private/
95
    public/
96
97
you may have write access in 'private' and 'public' but in 'Project'
98
itself. In that case, you can add the following in your locations.conf or
99
branch.conf file:
100
101
  upload_revid_location = private/.bzr-upload.revid
102
103
104
Upload from Remote Location
105
---------------------------
106
107
It is possible to upload to a remote location from another remote location by
108
specifying it with the --directory option:
109
110
    bzr upload ftp://public.example.com --directory sftp://private.example.com 
111
112
This, together with --auto, can be used to upload when you push to your
113
central branch, rather than when you commit to your local branch.
114
115
Note that you will consume more bandwith this way than uploading from a local
116
branch.
117
0.160.5 by Martin Albisetti
Add docs
118
Ignoring certain files
119
-----------------------
120
121
If you want to version a file, but not upload it, you can create a file called
122
.bzrignore-upload, which works in the same way as the regular .bzrignore file,
123
but only applies to bzr-upload.
124
125
0.152.63 by Vincent Ladeuil
Make the doc more easily discoverable.
126
Collaborating
127
-------------
128
129
While we don't have any platform setup, you can branch from trunk:
130
131
    bzr branch lp:bzr-upload
132
0.160.5 by Martin Albisetti
Add docs
133
And change anything you'd like, and file a merge proposal on Launchpad.
0.152.63 by Vincent Ladeuil
Make the doc more easily discoverable.
134
135
136
Known Issues
137
------------
138
0.162.1 by Vincent Ladeuil
Emit warnings instead of crashing when symlinks are encountered
139
 * Symlinks are not supported (warnings are emitted when they are encountered).
0.152.63 by Vincent Ladeuil
Make the doc more easily discoverable.
140
141
0.152.6 by Vincent Ladeuil
Really use the transports and test against all targeted protocols.
142
"""
0.152.1 by Vincent Ladeuil
Empty shell
143
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
144
# TODO: the chmod bits *can* be supported via the upload protocols
145
# (i.e. poorly), but since the web developers use these protocols to upload
146
# manually, it is expected that the associated web server is coherent with
147
# their presence/absence. In other words, if a web hosting provider requires
148
# chmod bits but don't provide an ftp server that support them, well, better
149
# find another provider ;-)
150
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
151
# TODO: The message emitted in verbose mode displays local paths. That may be
152
# scary for the user when we say 'Deleting <path>' and are referring to
153
# remote files...
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
154
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
155
from bzrlib import (
0.155.2 by James Westby
Add a post_change_branch_tip hook to upload.
156
    branch,
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
157
    commands,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
158
    lazy_import,
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
159
    option,
160
    )
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
161
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).
162
import stat
0.152.72 by Vincent Ladeuil
Avoid the recursive loading to be able to BZR_PLUGINS_AT.
163
import sys
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
164
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
165
from bzrlib import (
0.158.1 by Gary van der Merwe
Don't require a working tree.
166
    bzrdir,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
167
    errors,
0.161.4 by Vincent Ladeuil
Handle regexps in .bzrignore-upload and take parents into account.
168
    globbing,
0.160.3 by Martin Albisetti
Start implementing
169
    ignores,
0.152.73 by Vincent Ladeuil
Fix imports (order and a missing one).
170
    osutils,
171
    revision,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
172
    revisionspec,
0.152.73 by Vincent Ladeuil
Fix imports (order and a missing one).
173
    trace,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
174
    transport,
0.152.40 by Martin Albisetti
We need to import urlutils if we are going to use it
175
    urlutils,
0.152.27 by Martin Albisetti
* Added error message if the working tree has uncommited changes
176
    workingtree,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
177
    )
178
""")
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
179
0.152.54 by Vincent Ladeuil
Let's start dev for a 1.0.
180
version_info = (1, 0, 0, 'dev', 0)
0.152.23 by Martin Albisetti
Added version_info and plugin_name
181
plugin_name = 'upload'
182
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
183
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
184
def _get_branch_option(branch, option):
185
    return branch.get_config().get_user_option(option)
186
0.152.64 by Vincent Ladeuil
Fix compatibility with bzr.
187
# FIXME: Get rid of that as soon as we depend on a bzr API that includes
188
# get_user_option_as_bool
189
def _get_branch_bool_option(branch, option):
190
    conf = branch.get_config()
191
    if hasattr(conf, 'get_user_option_as_bool'):
192
        value = conf.get_user_option_as_bool(option)
193
    else:
194
        value = conf.get_user_option(option)
195
        if value is not None:
196
            if value.lower().strip() == 'true':
197
                value = True
198
            else:
199
                value = False
200
    return value
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
201
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
202
def _set_branch_option(branch, option, value):
203
    branch.get_config().set_user_option(option, value)
204
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
205
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
206
def get_upload_location(branch):
207
    return _get_branch_option(branch, 'upload_location')
208
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
209
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
210
def set_upload_location(branch, location):
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
211
    _set_branch_option(branch, 'upload_location', location)
212
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
213
0.152.62 by Vincent Ladeuil
Fix bug #423331 by adding a way to configure the path used to
214
# FIXME: Add more tests around invalid paths used here or relative paths that
215
# doesn't exist on remote (if only to get proper error messages)
216
def get_upload_revid_location(branch):
217
    loc =  _get_branch_option(branch, 'upload_revid_location')
218
    if loc is None:
219
        loc = '.bzr-upload.revid'
220
    return loc
221
222
223
def set_upload_revid_location(branch, location):
224
    _set_branch_option(branch, 'upload_revid_location', location)
225
226
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
227
def get_upload_auto(branch):
0.152.64 by Vincent Ladeuil
Fix compatibility with bzr.
228
    auto = _get_branch_bool_option(branch, 'upload_auto')
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
229
    if auto is None:
230
        auto = False # Default to False if not specified
231
    return auto
232
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
233
234
def set_upload_auto(branch, auto):
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
235
    # FIXME: What's the point in allowing a boolean here instead of requiring
236
    # the callers to use strings instead ?
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
237
    if auto:
238
        auto_str = "True"
239
    else:
240
        auto_str = "False"
241
    _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.
242
243
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
244
def get_upload_auto_quiet(branch):
0.152.64 by Vincent Ladeuil
Fix compatibility with bzr.
245
    quiet = _get_branch_bool_option(branch, 'upload_auto_quiet')
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
246
    if quiet is None:
247
        quiet = False # Default to False if not specified
248
    return quiet
249
250
251
def set_upload_auto_quiet(branch, quiet):
252
    _set_branch_option(branch, 'upload_auto_quiet', quiet)
253
254
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
255
class BzrUploader(object):
256
257
    def __init__(self, branch, to_transport, outf, tree, rev_id,
0.152.62 by Vincent Ladeuil
Fix bug #423331 by adding a way to configure the path used to
258
                 quiet=False):
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
259
        self.branch = branch
260
        self.to_transport = to_transport
261
        self.outf = outf
262
        self.tree = tree
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
263
        self.rev_id = rev_id
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
264
        self.quiet = quiet
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
265
        self._pending_deletions = []
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
266
        self._pending_renames = []
0.159.1 by Gary van der Merwe
Cache the uploaded_revid, so that we can query it more that once, with out having to worry about extra network calls.
267
        self._uploaded_revid = None
0.161.4 by Vincent Ladeuil
Handle regexps in .bzrignore-upload and take parents into account.
268
        self._ignored = None
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
269
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
270
    def _up_stat(self, relpath):
271
        return self.to_transport.stat(urlutils.escape(relpath))
272
273
    def _up_rename(self, old_path, new_path):
274
        return self.to_transport.rename(urlutils.escape(old_path),
275
                                        urlutils.escape(new_path))
276
277
    def _up_delete(self, relpath):
278
        return self.to_transport.delete(urlutils.escape(relpath))
279
280
    def _up_delete_tree(self, relpath):
281
        return self.to_transport.delete_tree(urlutils.escape(relpath))
282
283
    def _up_mkdir(self, relpath, mode):
284
        return self.to_transport.mkdir(urlutils.escape(relpath), mode)
285
286
    def _up_rmdir(self, relpath):
287
        return self.to_transport.rmdir(urlutils.escape(relpath))
288
289
    def _up_put_bytes(self, relpath, bytes, mode):
290
        self.to_transport.put_bytes(urlutils.escape(relpath), bytes, mode)
291
292
    def _up_get_bytes(self, relpath):
293
        return self.to_transport.get_bytes(urlutils.escape(relpath))
294
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
295
    def set_uploaded_revid(self, rev_id):
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
296
        # XXX: Add tests for concurrent updates, etc.
0.152.62 by Vincent Ladeuil
Fix bug #423331 by adding a way to configure the path used to
297
        revid_path = get_upload_revid_location(self.branch)
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
298
        self.to_transport.put_bytes(urlutils.escape(revid_path), rev_id)
0.159.1 by Gary van der Merwe
Cache the uploaded_revid, so that we can query it more that once, with out having to worry about extra network calls.
299
        self._uploaded_revid = rev_id
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
300
301
    def get_uploaded_revid(self):
0.159.1 by Gary van der Merwe
Cache the uploaded_revid, so that we can query it more that once, with out having to worry about extra network calls.
302
        if self._uploaded_revid is None:
303
            revid_path = get_upload_revid_location(self.branch)
0.159.2 by Gary van der Merwe
If there has not been an upload, treat the uploaded revid as revision.NULL_REVISION. This enables this request to be cached.
304
            try:
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
305
                self._uploaded_revid = self._up_get_bytes(revid_path)
0.159.2 by Gary van der Merwe
If there has not been an upload, treat the uploaded revid as revision.NULL_REVISION. This enables this request to be cached.
306
            except errors.NoSuchFile:
307
                # We have not upload to here.
308
                self._uploaded_revid = revision.NULL_REVISION
0.159.1 by Gary van der Merwe
Cache the uploaded_revid, so that we can query it more that once, with out having to worry about extra network calls.
309
        return self._uploaded_revid
0.152.7 by Vincent Ladeuil
Slight refactoring.
310
0.161.4 by Vincent Ladeuil
Handle regexps in .bzrignore-upload and take parents into account.
311
    def _get_ignored(self):
312
        if self._ignored is None:
313
            try:
314
                ignore_file = self.tree.get_file_by_path('.bzrignore-upload')
315
                ignored_patterns = ignores.parse_ignore_file(ignore_file)
316
            except errors.NoSuchId:
317
                ignored_patterns = []
318
            self._ignored = globbing.Globster(ignored_patterns)
319
        return self._ignored
320
321
    def is_ignored(self, relpath):
322
        glob = self._get_ignored()
323
        ignored = glob.match(relpath)
324
        import os
325
        if not ignored:
326
            # We still need to check that all parents are not ignored
327
            dir = os.path.dirname(relpath)
328
            while dir and not ignored:
329
                ignored = glob.match(dir)
330
                if not ignored:
331
                    dir = os.path.dirname(dir)
332
        return ignored
0.160.3 by Martin Albisetti
Start implementing
333
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
334
    def upload_file(self, relpath, id, mode=None):
0.161.5 by Vincent Ladeuil
Cleanup.
335
        if mode is None:
336
            if self.tree.is_executable(id):
337
                mode = 0775
338
            else:
339
                mode = 0664
340
        if not self.quiet:
341
            self.outf.write('Uploading %s\n' % relpath)
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
342
        self._up_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.
343
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
344
    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).
345
        """Upload a file, clearing the way on the remote side.
346
347
        When doing a full upload, it may happen that a directory exists where
348
        we want to put our file.
349
        """
350
        try:
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
351
            st = self._up_stat(relpath)
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
352
            if stat.S_ISDIR(st.st_mode):
353
                # A simple rmdir may not be enough
354
                if not self.quiet:
355
                    self.outf.write('Clearing %s/%s\n' % (
356
                            self.to_transport.external_url(), relpath))
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
357
                self._up_delete_tree(relpath)
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
358
        except errors.PathError:
359
            pass
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
360
        self.upload_file(relpath, id, mode)
361
362
    def make_remote_dir(self, relpath, mode=None):
0.161.5 by Vincent Ladeuil
Cleanup.
363
        if mode is None:
364
            mode = 0775
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
365
        self._up_mkdir(relpath, mode)
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
366
367
    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).
368
        """Create a remote directory, clearing the way on the remote side.
369
370
        When doing a full upload, it may happen that a file exists where we
371
        want to create our directory.
372
        """
373
        try:
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
374
            st = self._up_stat(relpath)
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
375
            if not stat.S_ISDIR(st.st_mode):
376
                if not self.quiet:
377
                    self.outf.write('Deleting %s/%s\n' % (
378
                            self.to_transport.external_url(), relpath))
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
379
                self._up_delete(relpath)
0.152.47 by Vincent Ladeuil
Don't fail a full upload on an already existing dir.
380
            else:
381
                # Ok the remote dir already exists, nothing to do
382
                return
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
383
        except errors.PathError:
384
            pass
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
385
        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).
386
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
387
    def delete_remote_file(self, relpath):
0.161.5 by Vincent Ladeuil
Cleanup.
388
        if not self.quiet:
389
            self.outf.write('Deleting %s\n' % relpath)
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
390
        self._up_delete(relpath)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
391
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
392
    def delete_remote_dir(self, relpath):
0.161.5 by Vincent Ladeuil
Cleanup.
393
        if not self.quiet:
394
            self.outf.write('Deleting %s\n' % relpath)
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
395
        self._up_rmdir(relpath)
0.161.5 by Vincent Ladeuil
Cleanup.
396
        # XXX: Add a test where a subdir is ignored but we still want to
397
        # delete the dir -- vila 100106
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
398
399
    def delete_remote_dir_maybe(self, relpath):
400
        """Try to delete relpath, keeping failures to retry later."""
401
        try:
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
402
            self._up_rmdir(relpath)
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
403
        # any kind of PathError would be OK, though we normally expect
404
        # DirectoryNotEmpty
405
        except errors.PathError:
406
            self._pending_deletions.append(relpath)
407
408
    def finish_deletions(self):
409
        if self._pending_deletions:
410
            # Process the previously failed deletions in reverse order to
411
            # delete children before parents
412
            for relpath in reversed(self._pending_deletions):
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
413
                self._up_rmdir(relpath)
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
414
            # The following shouldn't be needed since we use it once per
415
            # upload, but better safe than sorry ;-)
416
            self._pending_deletions = []
417
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
418
    def rename_remote(self, old_relpath, new_relpath):
419
        """Rename a remote file or directory taking care of collisions.
420
421
        To avoid collisions during bulk renames, each renamed target is
422
        temporarily assigned a unique name. When all renames have been done,
423
        each target get its proper name.
424
        """
425
        # We generate a sufficiently random name to *assume* that
426
        # no collisions will occur and don't worry about it (nor
427
        # handle it).
428
        import os
429
        import random
430
        import time
431
432
        stamp = '.tmp.%.9f.%d.%d' % (time.time(),
433
                                     os.getpid(),
434
                                     random.randint(0,0x7FFFFFFF))
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
435
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
436
            self.outf.write('Renaming %s to %s\n' % (old_relpath, new_relpath))
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
437
        self._up_rename(old_relpath, stamp)
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
438
        self._pending_renames.append((stamp, new_relpath))
439
440
    def finish_renames(self):
441
        for (stamp, new_path) in self._pending_renames:
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
442
            self._up_rename(stamp, new_path)
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
443
        # The following shouldn't be needed since we use it once per upload,
444
        # but better safe than sorry ;-)
445
        self._pending_renames = []
446
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
447
    def upload_full_tree(self):
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
448
        self.to_transport.ensure_base() # XXX: Handle errors (add
449
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
450
        self.tree.lock_read()
451
        try:
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
452
            for relpath, ie in self.tree.inventory.iter_entries():
0.160.4 by Martin Albisetti
Test passes!
453
                if relpath in ('', '.bzrignore', '.bzrignore-upload'):
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
454
                    # skip root ('')
0.161.4 by Vincent Ladeuil
Handle regexps in .bzrignore-upload and take parents into account.
455
                    # .bzrignore and .bzrignore-upload have no meaning outside
456
                    # a working tree so do not upload them
457
                    continue
458
                if self.is_ignored(relpath):
459
                    if not self.quiet:
460
                        self.outf.write('Ignoring %s\n' % relpath)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
461
                    continue
0.152.8 by Vincent Ladeuil
Handle uploading directories.
462
                if ie.kind == 'file':
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
463
                    self.upload_file_robustly(relpath, ie.file_id)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
464
                elif ie.kind == 'directory':
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
465
                    self.make_remote_dir_robustly(relpath)
0.162.1 by Vincent Ladeuil
Emit warnings instead of crashing when symlinks are encountered
466
                elif ie.kind == 'symlink':
467
                    if not self.quiet:
468
                        target = self.tree.path_content_summary(path)[3]
469
                        self.outf.write('Not uploading symlink %s -> %s\n'
470
                                        % (path, target))
0.152.8 by Vincent Ladeuil
Handle uploading directories.
471
                else:
472
                    raise NotImplementedError
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
473
            self.set_uploaded_revid(self.rev_id)
474
        finally:
475
            self.tree.unlock()
476
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
477
    def upload_tree(self):
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
478
        # If we can't find the revid file on the remote location, upload the
479
        # full tree instead
0.159.2 by Gary van der Merwe
If there has not been an upload, treat the uploaded revid as revision.NULL_REVISION. This enables this request to be cached.
480
        rev_id = self.get_uploaded_revid()
0.160.2 by Martin Albisetti
Clean up whitespace to appease the neurotic in me
481
0.159.2 by Gary van der Merwe
If there has not been an upload, treat the uploaded revid as revision.NULL_REVISION. This enables this request to be cached.
482
        if rev_id == revision.NULL_REVISION:
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
483
            if not self.quiet:
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
484
                self.outf.write('No uploaded revision id found,'
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
485
                                ' switching to full upload\n')
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
486
            self.upload_full_tree()
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
487
            # We're done
488
            return
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
489
0.152.35 by Martin Albisetti
* Tell the user if the remote location is already up to date
490
        # Check if the revision hasn't already been uploaded
491
        if rev_id == self.rev_id:
492
            if not self.quiet:
493
                self.outf.write('Remote location already up to date\n')
494
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
495
        from_tree = self.branch.repository.revision_tree(rev_id)
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
496
        self.to_transport.ensure_base() # XXX: Handle errors (add
497
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
498
        changes = self.tree.changes_from(from_tree)
499
        self.tree.lock_read()
500
        try:
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
501
            for (path, id, kind) in changes.removed:
0.161.4 by Vincent Ladeuil
Handle regexps in .bzrignore-upload and take parents into account.
502
                if self.is_ignored(path):
503
                    if not self.quiet:
504
                        self.outf.write('Ignoring %s\n' % path)
505
                    continue
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
506
                if kind is 'file':
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
507
                    self.delete_remote_file(path)
508
                elif kind is  'directory':
509
                    self.delete_remote_dir_maybe(path)
0.162.1 by Vincent Ladeuil
Emit warnings instead of crashing when symlinks are encountered
510
                elif kind == 'symlink':
511
                    if not self.quiet:
512
                        target = self.tree.path_content_summary(path)[3]
513
                        self.outf.write('Not deleting remote symlink %s -> %s\n'
514
                                        % (path, target))
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
515
                else:
516
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
517
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
518
            for (old_path, new_path, id, kind,
519
                 content_change, exec_change) in changes.renamed:
0.161.4 by Vincent Ladeuil
Handle regexps in .bzrignore-upload and take parents into account.
520
                if self.is_ignored(old_path) and self.is_ignored(new_path):
521
                    if not self.quiet:
522
                        self.outf.write('Ignoring %s\n' % old_path)
523
                        self.outf.write('Ignoring %s\n' % new_path)
524
                    continue
0.152.52 by Vincent Ladeuil
Fix bug #270219 by handling content changes during renames.
525
                if content_change:
526
                    # We update the old_path content because renames and
527
                    # deletions are differed.
528
                    self.upload_file(old_path, id)
0.162.1 by Vincent Ladeuil
Emit warnings instead of crashing when symlinks are encountered
529
                if kind == 'symlink':
530
                    if not self.quiet:
531
                        self.outf.write('Not renaming remote symlink %s to %s\n'
532
                                        % (old_path, new_path))
533
                else:
534
                    self.rename_remote(old_path, new_path)
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
535
            self.finish_renames()
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
536
            self.finish_deletions()
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
537
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
538
            for (path, id, old_kind, new_kind) in changes.kind_changed:
0.161.4 by Vincent Ladeuil
Handle regexps in .bzrignore-upload and take parents into account.
539
                if self.is_ignored(path):
540
                    if not self.quiet:
541
                        self.outf.write('Ignoring %s\n' % path)
542
                    continue
0.162.1 by Vincent Ladeuil
Emit warnings instead of crashing when symlinks are encountered
543
                if old_kind == 'file':
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
544
                    self.delete_remote_file(path)
0.162.1 by Vincent Ladeuil
Emit warnings instead of crashing when symlinks are encountered
545
                elif old_kind ==  'directory':
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
546
                    self.delete_remote_dir(path)
547
                else:
548
                    raise NotImplementedError
549
0.162.1 by Vincent Ladeuil
Emit warnings instead of crashing when symlinks are encountered
550
                if new_kind == 'file':
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
551
                    self.upload_file(path, id)
552
                elif new_kind is 'directory':
553
                    self.make_remote_dir(path)
554
                else:
555
                    raise NotImplementedError
556
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
557
            for (path, id, kind) in changes.added:
0.161.4 by Vincent Ladeuil
Handle regexps in .bzrignore-upload and take parents into account.
558
                if self.is_ignored(path):
559
                    if not self.quiet:
560
                        self.outf.write('Ignoring %s\n' % path)
561
                    continue
0.162.1 by Vincent Ladeuil
Emit warnings instead of crashing when symlinks are encountered
562
                if kind == 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
563
                    self.upload_file(path, id)
0.162.1 by Vincent Ladeuil
Emit warnings instead of crashing when symlinks are encountered
564
                elif kind == 'directory':
0.152.8 by Vincent Ladeuil
Handle uploading directories.
565
                    self.make_remote_dir(path)
0.162.1 by Vincent Ladeuil
Emit warnings instead of crashing when symlinks are encountered
566
                elif kind == 'symlink':
567
                    if not self.quiet:
568
                        target = self.tree.path_content_summary(path)[3]
569
                        self.outf.write('Not uploading symlink %s -> %s\n'
570
                                        % (path, target))
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
571
                else:
572
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
573
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
574
            # XXX: Add a test for exec_change
575
            for (path, id, kind,
576
                 content_change, exec_change) in changes.modified:
0.161.4 by Vincent Ladeuil
Handle regexps in .bzrignore-upload and take parents into account.
577
                if self.is_ignored(path):
578
                    if not self.quiet:
579
                        self.outf.write('Ignoring %s\n' % path)
580
                    continue
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
581
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
582
                    self.upload_file(path, id)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
583
                else:
584
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
585
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
586
            self.set_uploaded_revid(self.rev_id)
587
        finally:
588
            self.tree.unlock()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
589
0.152.57 by Vincent Ladeuil
Fix minor 2.4 compatibility bug.
590
0.152.68 by Vincent Ladeuil
Cleanup some pending changes.
591
class CannotUploadToWorkingTree(errors.BzrCommandError):
0.158.19 by Vincent Ladeuil
Bzr has facilities for exceptions, let's use them.
592
593
    _fmt = 'Cannot upload to a bzr managed working tree: %(url)s".'
594
0.152.1 by Vincent Ladeuil
Empty shell
595
0.152.68 by Vincent Ladeuil
Cleanup some pending changes.
596
class DivergedUploadedTree(errors.BzrCommandError):
597
598
    _fmt = ("Your branch (%(revid)s)"
599
            " and the uploaded tree (%(uploaded_revid)s) have diverged: ")
600
601
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
602
class cmd_upload(commands.Command):
603
    """Upload a working tree, as a whole or incrementally.
604
605
    If no destination is specified use the last one used.
606
    If no revision is specified upload the changes since the last upload.
0.152.56 by Vincent Ladeuil
Small tweaks including doc fix (#275538).
607
608
    Changes include files added, renamed, modified or removed.
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
609
    """
0.152.63 by Vincent Ladeuil
Make the doc more easily discoverable.
610
    _see_also = ['plugins/upload']
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
611
    takes_args = ['location?']
612
    takes_options = [
613
        'revision',
614
        'remember',
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
615
        'overwrite',
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
616
        option.Option('full', 'Upload the full working tree.'),
617
        option.Option('quiet', 'Do not output what is being done.',
618
                       short_name='q'),
619
        option.Option('directory',
620
                      help='Branch to upload from, '
621
                      'rather than the one containing the working directory.',
622
                      short_name='d',
623
                      type=unicode,
624
                      ),
0.155.5 by James Westby
Hook up the --upload option and document it.
625
        option.Option('auto',
626
                      'Trigger an upload from this branch whenever the tip '
627
                      'revision changes.')
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
628
       ]
629
630
    def run(self, location=None, full=False, revision=None, remember=None,
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
631
            directory=None, quiet=False, auto=None, overwrite=False
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
632
            ):
633
        if directory is None:
634
            directory = u'.'
0.158.7 by Gary van der Merwe
Clean up white space.
635
0.157.1 by James Westby
Don't try and register the hook if install_named_hook is not available
636
        if auto and not auto_hook_available:
637
            raise BzrCommandError("Your version of bzr does not have the "
638
                    "hooks necessary for --auto to work")
639
0.158.19 by Vincent Ladeuil
Bzr has facilities for exceptions, let's use them.
640
        (wt, branch,
641
         relpath) = bzrdir.BzrDir.open_containing_tree_or_branch(directory)
0.160.2 by Martin Albisetti
Clean up whitespace to appease the neurotic in me
642
0.158.1 by Gary van der Merwe
Don't require a working tree.
643
        if wt:
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
644
            wt.lock_read()
0.152.68 by Vincent Ladeuil
Cleanup some pending changes.
645
            locked = wt
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
646
        else:
647
            branch.lock_read()
0.152.68 by Vincent Ladeuil
Cleanup some pending changes.
648
            locked = branch
0.158.9 by Gary van der Merwe
Add a check to make sure we are not uploading to an existing wt.
649
        try:
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
650
            if wt:
651
                changes = wt.changes_from(wt.basis_tree())
0.160.2 by Martin Albisetti
Clean up whitespace to appease the neurotic in me
652
0.161.2 by Martin Albisetti
Remove commented lines for debugging
653
                if revision is None and  changes.has_changed():
654
                    raise errors.UncommittedChanges(wt)
0.160.2 by Martin Albisetti
Clean up whitespace to appease the neurotic in me
655
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
656
            if location is None:
657
                stored_loc = get_upload_location(branch)
658
                if stored_loc is None:
0.164.1 by Vincent Ladeuil
Add a test to cover bug #600628 but it is already fixed.
659
                    raise errors.BzrCommandError(
660
                        'No upload location known or specified.')
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
661
                else:
662
                    # FIXME: Not currently tested
663
                    display_url = urlutils.unescape_for_display(stored_loc,
664
                            self.outf.encoding)
665
                    self.outf.write("Using saved location: %s\n" % display_url)
666
                    location = stored_loc
0.160.2 by Martin Albisetti
Clean up whitespace to appease the neurotic in me
667
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
668
            to_transport = transport.get_transport(location)
0.160.2 by Martin Albisetti
Clean up whitespace to appease the neurotic in me
669
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
670
            # Check that we are not uploading to a existing working tree.
671
            try:
672
                to_bzr_dir = bzrdir.BzrDir.open_from_transport(to_transport)
673
                has_wt = to_bzr_dir.has_workingtree()
674
            except errors.NotBranchError:
675
                has_wt = False
676
            except errors.NotLocalUrl:
677
                # The exception raised is a bit weird... but that's life.
678
                has_wt = True
0.160.2 by Martin Albisetti
Clean up whitespace to appease the neurotic in me
679
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
680
            if has_wt:
0.152.68 by Vincent Ladeuil
Cleanup some pending changes.
681
                raise CannotUploadToWorkingTree(url=location)
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
682
            if revision is None:
683
                rev_id = branch.last_revision()
684
            else:
685
                if len(revision) != 1:
686
                    raise errors.BzrCommandError(
687
                        'bzr upload --revision takes exactly 1 argument')
688
                rev_id = revision[0].in_history(branch).rev_id
0.160.2 by Martin Albisetti
Clean up whitespace to appease the neurotic in me
689
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
690
            tree = branch.repository.revision_tree(rev_id)
0.160.2 by Martin Albisetti
Clean up whitespace to appease the neurotic in me
691
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
692
            uploader = BzrUploader(branch, to_transport, self.outf, tree,
693
                                   rev_id, quiet=quiet)
0.160.2 by Martin Albisetti
Clean up whitespace to appease the neurotic in me
694
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
695
            if not overwrite:
696
                prev_uploaded_rev_id = uploader.get_uploaded_revid()
697
                graph = branch.repository.get_graph()
698
                if not graph.is_ancestor(prev_uploaded_rev_id, rev_id):
0.152.68 by Vincent Ladeuil
Cleanup some pending changes.
699
                    raise DivergedUploadedTree(
700
                        revid=rev_id, uploaded_revid=prev_uploaded_rev_id)
0.159.4 by Gary van der Merwe
Check that the revision we are uploading is a descendent from the revision that was uploaded.
701
            if full:
702
                uploader.upload_full_tree()
703
            else:
704
                uploader.upload_tree()
705
        finally:
0.152.68 by Vincent Ladeuil
Cleanup some pending changes.
706
            locked.unlock()
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
707
708
        # We uploaded successfully, remember it
709
        if get_upload_location(branch) is None or remember:
0.152.70 by Vincent Ladeuil
Ensure we use the transport API correctly for unicode paths.
710
            set_upload_location(branch, urlutils.unescape(to_transport.base))
0.155.4 by James Westby
Add the groundwork for --auto that enables the hook for a branch.
711
        if auto is not None:
712
            set_upload_auto(branch, auto)
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
713
714
0.152.1 by Vincent Ladeuil
Empty shell
715
commands.register_command(cmd_upload)
716
0.152.68 by Vincent Ladeuil
Cleanup some pending changes.
717
0.152.72 by Vincent Ladeuil
Avoid the recursive loading to be able to BZR_PLUGINS_AT.
718
def auto_upload_hook(params):
719
    source_branch = params.branch
720
    destination = get_upload_location(source_branch)
721
    if destination is None:
722
        return
723
    auto_upload = get_upload_auto(source_branch)
724
    if not auto_upload:
725
        return
726
    quiet = get_upload_auto_quiet(source_branch)
727
    if not quiet:
728
        display_url = urlutils.unescape_for_display(
729
            destination, osutils.get_terminal_encoding())
730
        trace.note('Automatically uploading to %s', display_url)
731
    to_transport = transport.get_transport(destination)
732
    last_revision = source_branch.last_revision()
733
    last_tree = source_branch.repository.revision_tree(last_revision)
734
    uploader = BzrUploader(source_branch, to_transport, sys.stdout,
735
                           last_tree, last_revision, quiet=quiet)
736
    uploader.upload_tree()
737
738
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
739
def install_auto_upload_hook():
0.157.1 by James Westby
Don't try and register the hook if install_named_hook is not available
740
    branch.Branch.hooks.install_named_hook('post_change_branch_tip',
0.152.72 by Vincent Ladeuil
Avoid the recursive loading to be able to BZR_PLUGINS_AT.
741
            auto_upload_hook,
0.157.1 by James Westby
Don't try and register the hook if install_named_hook is not available
742
            'Auto upload code from a branch when it is changed.')
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
743
744
745
if hasattr(branch.Branch.hooks, "install_named_hook"):
746
    install_auto_upload_hook()
0.157.1 by James Westby
Don't try and register the hook if install_named_hook is not available
747
    auto_hook_available = True
748
else:
749
    auto_hook_available = False
0.155.2 by James Westby
Add a post_change_branch_tip hook to upload.
750
751
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
752
def load_tests(basic_tests, module, loader):
0.153.1 by Vincent Ladeuil
Clean up references to verbose.
753
    # 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.
754
    # that. I prefer to update basic_tests with the other tests to detect
755
    # unwanted tests and I think that's sufficient.
0.152.1 by Vincent Ladeuil
Empty shell
756
757
    testmod_names = [
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
758
        'tests',
0.152.1 by Vincent Ladeuil
Empty shell
759
        ]
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
760
    basic_tests.addTest(loader.loadTestsFromModuleNames(
0.152.1 by Vincent Ladeuil
Empty shell
761
            ["%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.
762
    return basic_tests