/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
1
# Copyright (C) 2008, 2009 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
42
43
Automatically Uploading
44
-----------------------
45
46
bzr-upload comes with a hook that can be used to trigger an upload whenever
47
the tip of the branch changes, including on commit, push, uncommit etc. This
48
would allow you to keep the code on the target up to date automatically.
49
50
The easiest way to enable this is to run upload with the --auto option.
51
52
     bzr upload --auto
53
54
will enable the hook for this branch. If you were to do a commit in this branch
55
now you would see it trigger the upload automatically.
56
57
If you wish to disable this for a branch again then you can use the --no-auto
58
option.
59
60
     bzr upload --no-auto
61
62
will disable the feature for that branch.
63
64
Since the auto hook is triggered automatically, you can't use the --quiet
65
option available for the upload command. Instead, you can set the
66
'upload_auto_quiet' configuration variable to True or False in either
67
bazaar.conf, locations.conf or branch.conf.
68
69
70
Storing the '.bzr-upload.revid' file
71
------------------------------------
72
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
73
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.
74
revision id. The uploaded working tree is not linked to any other bzr data.
75
0.152.63 by Vincent Ladeuil
Make the doc more easily discoverable.
76
If the layout of your remote server is such that you can't write in the
77
root directory but only in the directories inside that root, you will need
78
to use the 'upload_revid_location' configuration variable to specify the
79
relative path to be used. That configuration variable can be specified in
80
locations.conf or branch.conf.
81
82
For example, given the following layout:
83
84
  Project/
85
    private/
86
    public/
87
88
you may have write access in 'private' and 'public' but in 'Project'
89
itself. In that case, you can add the following in your locations.conf or
90
branch.conf file:
91
92
  upload_revid_location = private/.bzr-upload.revid
93
94
95
Upload from Remote Location
96
---------------------------
97
98
It is possible to upload to a remote location from another remote location by
99
specifying it with the --directory option:
100
101
    bzr upload ftp://public.example.com --directory sftp://private.example.com 
102
103
This, together with --auto, can be used to upload when you push to your
104
central branch, rather than when you commit to your local branch.
105
106
Note that you will consume more bandwith this way than uploading from a local
107
branch.
108
109
Collaborating
110
-------------
111
112
While we don't have any platform setup, you can branch from trunk:
113
114
    bzr branch lp:bzr-upload
115
116
And change anything you'd like, and get in touch with any of the authors to 
117
review and add the changes.
118
119
120
Known Issues
121
------------
122
123
 * Symlinks are not supported
124
125
0.152.6 by Vincent Ladeuil
Really use the transports and test against all targeted protocols.
126
"""
0.152.1 by Vincent Ladeuil
Empty shell
127
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
128
# TODO: the chmod bits *can* be supported via the upload protocols
129
# (i.e. poorly), but since the web developers use these protocols to upload
130
# manually, it is expected that the associated web server is coherent with
131
# their presence/absence. In other words, if a web hosting provider requires
132
# chmod bits but don't provide an ftp server that support them, well, better
133
# find another provider ;-)
134
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
135
# TODO: The message emitted in verbose mode displays local paths. That may be
136
# scary for the user when we say 'Deleting <path>' and are referring to
137
# remote files...
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
138
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
139
from bzrlib import (
0.155.2 by James Westby
Add a post_change_branch_tip hook to upload.
140
    branch,
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
141
    commands,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
142
    lazy_import,
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
143
    option,
144
    )
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
145
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).
146
import stat
147
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
148
from bzrlib import (
0.158.1 by Gary van der Merwe
Don't require a working tree.
149
    bzrdir,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
150
    errors,
151
    revisionspec,
152
    transport,
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
153
    osutils,
0.152.40 by Martin Albisetti
We need to import urlutils if we are going to use it
154
    urlutils,
0.152.27 by Martin Albisetti
* Added error message if the working tree has uncommited changes
155
    workingtree,
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.
156
    revision
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
157
    )
158
""")
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
159
0.152.54 by Vincent Ladeuil
Let's start dev for a 1.0.
160
version_info = (1, 0, 0, 'dev', 0)
0.152.23 by Martin Albisetti
Added version_info and plugin_name
161
plugin_name = 'upload'
162
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
163
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
164
def _get_branch_option(branch, option):
165
    return branch.get_config().get_user_option(option)
166
0.152.64 by Vincent Ladeuil
Fix compatibility with bzr.
167
# FIXME: Get rid of that as soon as we depend on a bzr API that includes
168
# get_user_option_as_bool
169
def _get_branch_bool_option(branch, option):
170
    conf = branch.get_config()
171
    if hasattr(conf, 'get_user_option_as_bool'):
172
        value = conf.get_user_option_as_bool(option)
173
    else:
174
        value = conf.get_user_option(option)
175
        if value is not None:
176
            if value.lower().strip() == 'true':
177
                value = True
178
            else:
179
                value = False
180
    return value
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
181
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
182
def _set_branch_option(branch, option, value):
183
    branch.get_config().set_user_option(option, value)
184
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
185
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
186
def get_upload_location(branch):
187
    return _get_branch_option(branch, 'upload_location')
188
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
189
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
190
def set_upload_location(branch, location):
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
191
    _set_branch_option(branch, 'upload_location', location)
192
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
193
0.152.62 by Vincent Ladeuil
Fix bug #423331 by adding a way to configure the path used to
194
# FIXME: Add more tests around invalid paths used here or relative paths that
195
# doesn't exist on remote (if only to get proper error messages)
196
def get_upload_revid_location(branch):
197
    loc =  _get_branch_option(branch, 'upload_revid_location')
198
    if loc is None:
199
        loc = '.bzr-upload.revid'
200
    return loc
201
202
203
def set_upload_revid_location(branch, location):
204
    _set_branch_option(branch, 'upload_revid_location', location)
205
206
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
207
def get_upload_auto(branch):
0.152.64 by Vincent Ladeuil
Fix compatibility with bzr.
208
    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.
209
    if auto is None:
210
        auto = False # Default to False if not specified
211
    return auto
212
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
213
214
def set_upload_auto(branch, auto):
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
215
    # FIXME: What's the point in allowing a boolean here instead of requiring
216
    # the callers to use strings instead ?
0.155.3 by James Westby
Add some tests for the hook, rename the option to "upload_auto"
217
    if auto:
218
        auto_str = "True"
219
    else:
220
        auto_str = "False"
221
    _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.
222
223
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
224
def get_upload_auto_quiet(branch):
0.152.64 by Vincent Ladeuil
Fix compatibility with bzr.
225
    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.
226
    if quiet is None:
227
        quiet = False # Default to False if not specified
228
    return quiet
229
230
231
def set_upload_auto_quiet(branch, quiet):
232
    _set_branch_option(branch, 'upload_auto_quiet', quiet)
233
234
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
235
class BzrUploader(object):
236
237
    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
238
                 quiet=False):
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
239
        self.branch = branch
240
        self.to_transport = to_transport
241
        self.outf = outf
242
        self.tree = tree
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
243
        self.rev_id = rev_id
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
244
        self.quiet = quiet
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
245
        self._pending_deletions = []
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
246
        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.
247
        self._uploaded_revid = None
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
248
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
249
    def set_uploaded_revid(self, rev_id):
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
250
        # 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
251
        revid_path = get_upload_revid_location(self.branch)
252
        self.to_transport.put_bytes(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.
253
        self._uploaded_revid = rev_id
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
254
255
    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.
256
        if self._uploaded_revid is None:
257
            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.
258
            try:
259
                self._uploaded_revid = self.to_transport.get_bytes(revid_path)
260
            except errors.NoSuchFile:
261
                # We have not upload to here.
262
                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.
263
        return self._uploaded_revid
0.152.7 by Vincent Ladeuil
Slight refactoring.
264
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
265
    def upload_file(self, relpath, id, mode=None):
266
        if mode is None:
267
            if self.tree.is_executable(id):
268
                mode = 0775
269
            else:
270
                mode = 0664
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
271
        if not self.quiet:
272
            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
273
        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.
274
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
275
    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).
276
        """Upload a file, clearing the way on the remote side.
277
278
        When doing a full upload, it may happen that a directory exists where
279
        we want to put our file.
280
        """
281
        try:
282
            st = self.to_transport.stat(relpath)
283
            if stat.S_ISDIR(st.st_mode):
284
                # A simple rmdir may not be enough
285
                if not self.quiet:
286
                    self.outf.write('Clearing %s/%s\n' % (
287
                            self.to_transport.external_url(), relpath))
288
                self.to_transport.delete_tree(relpath)
289
        except errors.PathError:
290
            pass
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
291
        self.upload_file(relpath, id, mode)
292
293
    def make_remote_dir(self, relpath, mode=None):
294
        if mode is None:
295
            mode = 0775
296
        self.to_transport.mkdir(relpath, mode)
297
298
    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).
299
        """Create a remote directory, clearing the way on the remote side.
300
301
        When doing a full upload, it may happen that a file exists where we
302
        want to create our directory.
303
        """
304
        try:
305
            st = self.to_transport.stat(relpath)
306
            if not stat.S_ISDIR(st.st_mode):
307
                if not self.quiet:
308
                    self.outf.write('Deleting %s/%s\n' % (
309
                            self.to_transport.external_url(), relpath))
310
                self.to_transport.delete(relpath)
0.152.47 by Vincent Ladeuil
Don't fail a full upload on an already existing dir.
311
            else:
312
                # Ok the remote dir already exists, nothing to do
313
                return
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
314
        except errors.PathError:
315
            pass
0.152.46 by Vincent Ladeuil
Handle x mode bit for files and provides default mode bits for
316
        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).
317
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
318
    def delete_remote_file(self, relpath):
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
319
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
320
            self.outf.write('Deleting %s\n' % relpath)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
321
        self.to_transport.delete(relpath)
322
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
323
    def delete_remote_dir(self, relpath):
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
324
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
325
            self.outf.write('Deleting %s\n' % relpath)
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
326
        self.to_transport.rmdir(relpath)
327
328
    def delete_remote_dir_maybe(self, relpath):
329
        """Try to delete relpath, keeping failures to retry later."""
330
        try:
331
            self.to_transport.rmdir(relpath)
332
        # any kind of PathError would be OK, though we normally expect
333
        # DirectoryNotEmpty
334
        except errors.PathError:
335
            self._pending_deletions.append(relpath)
336
337
    def finish_deletions(self):
338
        if self._pending_deletions:
339
            # Process the previously failed deletions in reverse order to
340
            # delete children before parents
341
            for relpath in reversed(self._pending_deletions):
342
                self.to_transport.rmdir(relpath)
343
            # The following shouldn't be needed since we use it once per
344
            # upload, but better safe than sorry ;-)
345
            self._pending_deletions = []
346
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
347
    def rename_remote(self, old_relpath, new_relpath):
348
        """Rename a remote file or directory taking care of collisions.
349
350
        To avoid collisions during bulk renames, each renamed target is
351
        temporarily assigned a unique name. When all renames have been done,
352
        each target get its proper name.
353
        """
354
        # We generate a sufficiently random name to *assume* that
355
        # no collisions will occur and don't worry about it (nor
356
        # handle it).
357
        import os
358
        import random
359
        import time
360
361
        stamp = '.tmp.%.9f.%d.%d' % (time.time(),
362
                                     os.getpid(),
363
                                     random.randint(0,0x7FFFFFFF))
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
364
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
365
            self.outf.write('Renaming %s to %s\n' % (old_relpath, new_relpath))
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
366
        self.to_transport.rename(old_relpath, stamp)
367
        self._pending_renames.append((stamp, new_relpath))
368
369
    def finish_renames(self):
370
        for (stamp, new_path) in self._pending_renames:
371
            self.to_transport.rename(stamp, new_path)
372
        # The following shouldn't be needed since we use it once per upload,
373
        # but better safe than sorry ;-)
374
        self._pending_renames = []
375
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
376
    def upload_full_tree(self):
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
377
        self.to_transport.ensure_base() # XXX: Handle errors (add
378
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
379
        self.tree.lock_read()
380
        try:
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
381
            for relpath, ie in self.tree.inventory.iter_entries():
382
                if relpath in ('', '.bzrignore'):
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
383
                    # skip root ('')
384
                    # .bzrignore has no meaning outside of a working tree
385
                    # so do not upload it
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
386
                    continue
0.152.8 by Vincent Ladeuil
Handle uploading directories.
387
                if ie.kind == 'file':
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
388
                    self.upload_file_robustly(relpath, ie.file_id)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
389
                elif ie.kind == 'directory':
0.152.44 by Vincent Ladeuil
More robust full upload (at least regarding files changed to dirs and vice-versa).
390
                    self.make_remote_dir_robustly(relpath)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
391
                else:
392
                    raise NotImplementedError
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
393
            self.set_uploaded_revid(self.rev_id)
394
        finally:
395
            self.tree.unlock()
396
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
397
    def upload_tree(self):
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
398
        # If we can't find the revid file on the remote location, upload the
399
        # 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.
400
        rev_id = self.get_uploaded_revid()
401
        
402
        if rev_id == revision.NULL_REVISION:
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
403
            if not self.quiet:
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
404
                self.outf.write('No uploaded revision id found,'
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
405
                                ' switching to full upload\n')
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
406
            self.upload_full_tree()
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
407
            # We're done
408
            return
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
409
0.152.35 by Martin Albisetti
* Tell the user if the remote location is already up to date
410
        # Check if the revision hasn't already been uploaded
411
        if rev_id == self.rev_id:
412
            if not self.quiet:
413
                self.outf.write('Remote location already up to date\n')
414
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
415
        # XXX: errors out if rev_id not in branch history (probably someone
416
        # uploaded from a different branch).
417
        from_tree = self.branch.repository.revision_tree(rev_id)
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
418
        self.to_transport.ensure_base() # XXX: Handle errors (add
419
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
420
        changes = self.tree.changes_from(from_tree)
421
        self.tree.lock_read()
422
        try:
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
423
            for (path, id, kind) in changes.removed:
424
                if kind is 'file':
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
425
                    self.delete_remote_file(path)
426
                elif kind is  'directory':
427
                    self.delete_remote_dir_maybe(path)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
428
                else:
429
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
430
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
431
            for (old_path, new_path, id, kind,
432
                 content_change, exec_change) in changes.renamed:
0.152.52 by Vincent Ladeuil
Fix bug #270219 by handling content changes during renames.
433
                if content_change:
434
                    # We update the old_path content because renames and
435
                    # deletions are differed.
436
                    self.upload_file(old_path, id)
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
437
                self.rename_remote(old_path, new_path)
438
            self.finish_renames()
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
439
            self.finish_deletions()
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
440
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
441
            for (path, id, old_kind, new_kind) in changes.kind_changed:
442
                if old_kind is 'file':
443
                    self.delete_remote_file(path)
444
                elif old_kind is  'directory':
445
                    self.delete_remote_dir(path)
446
                else:
447
                    raise NotImplementedError
448
449
                if new_kind is 'file':
450
                    self.upload_file(path, id)
451
                elif new_kind is 'directory':
452
                    self.make_remote_dir(path)
453
                else:
454
                    raise NotImplementedError
455
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
456
            for (path, id, kind) in changes.added:
457
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
458
                    self.upload_file(path, id)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
459
                elif kind is 'directory':
460
                    self.make_remote_dir(path)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
461
                else:
462
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
463
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
464
            # XXX: Add a test for exec_change
465
            for (path, id, kind,
466
                 content_change, exec_change) in changes.modified:
467
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
468
                    self.upload_file(path, id)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
469
                else:
470
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
471
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
472
            self.set_uploaded_revid(self.rev_id)
473
        finally:
474
            self.tree.unlock()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
475
0.152.57 by Vincent Ladeuil
Fix minor 2.4 compatibility bug.
476
0.158.19 by Vincent Ladeuil
Bzr has facilities for exceptions, let's use them.
477
class CannotUploadToWorkingTreeError(errors.BzrCommandError):
478
479
    _fmt = 'Cannot upload to a bzr managed working tree: %(url)s".'
480
0.152.1 by Vincent Ladeuil
Empty shell
481
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
482
class cmd_upload(commands.Command):
483
    """Upload a working tree, as a whole or incrementally.
484
485
    If no destination is specified use the last one used.
486
    If no revision is specified upload the changes since the last upload.
0.152.56 by Vincent Ladeuil
Small tweaks including doc fix (#275538).
487
488
    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.
489
    """
0.152.63 by Vincent Ladeuil
Make the doc more easily discoverable.
490
    _see_also = ['plugins/upload']
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
491
    takes_args = ['location?']
492
    takes_options = [
493
        'revision',
494
        'remember',
495
        option.Option('full', 'Upload the full working tree.'),
496
        option.Option('quiet', 'Do not output what is being done.',
497
                       short_name='q'),
498
        option.Option('directory',
499
                      help='Branch to upload from, '
500
                      'rather than the one containing the working directory.',
501
                      short_name='d',
502
                      type=unicode,
503
                      ),
0.155.5 by James Westby
Hook up the --upload option and document it.
504
        option.Option('auto',
505
                      'Trigger an upload from this branch whenever the tip '
506
                      'revision changes.')
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
507
       ]
508
509
    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.
510
            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.
511
            ):
512
        if directory is None:
513
            directory = u'.'
0.158.7 by Gary van der Merwe
Clean up white space.
514
0.157.1 by James Westby
Don't try and register the hook if install_named_hook is not available
515
        if auto and not auto_hook_available:
516
            raise BzrCommandError("Your version of bzr does not have the "
517
                    "hooks necessary for --auto to work")
518
0.158.19 by Vincent Ladeuil
Bzr has facilities for exceptions, let's use them.
519
        (wt, branch,
520
         relpath) = bzrdir.BzrDir.open_containing_tree_or_branch(directory)
0.158.7 by Gary van der Merwe
Clean up white space.
521
0.158.1 by Gary van der Merwe
Don't require a working tree.
522
        if wt:
523
            changes = wt.changes_from(wt.basis_tree())
0.158.7 by Gary van der Merwe
Clean up white space.
524
0.158.1 by Gary van der Merwe
Don't require a working tree.
525
            if revision is None and  changes.has_changed():
526
                raise errors.UncommittedChanges(wt)
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
527
528
        if location is None:
529
            stored_loc = get_upload_location(branch)
530
            if stored_loc is None:
531
                raise errors.BzrCommandError('No upload location'
532
                                             ' known or specified.')
533
            else:
534
                # FIXME: Not currently tested
535
                display_url = urlutils.unescape_for_display(stored_loc,
536
                        self.outf.encoding)
537
                self.outf.write("Using saved location: %s\n" % display_url)
538
                location = stored_loc
539
540
        to_transport = transport.get_transport(location)
0.158.9 by Gary van der Merwe
Add a check to make sure we are not uploading to an existing wt.
541
542
        # Check that we are not uploading to a existing working tree.
543
        try:
544
            to_bzr_dir = bzrdir.BzrDir.open_from_transport(to_transport)
545
            has_wt = to_bzr_dir.has_workingtree()
546
        except errors.NotBranchError:
547
            has_wt = False
548
        except errors.NotLocalUrl:
0.158.19 by Vincent Ladeuil
Bzr has facilities for exceptions, let's use them.
549
            # The exception raised is a bit weird... but that's life.
0.158.9 by Gary van der Merwe
Add a check to make sure we are not uploading to an existing wt.
550
            has_wt = True
551
552
        if has_wt:
0.152.57 by Vincent Ladeuil
Fix minor 2.4 compatibility bug.
553
            raise CannotUploadToWorkingTreeError(url=location)
0.158.9 by Gary van der Merwe
Add a check to make sure we are not uploading to an existing wt.
554
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
555
        if revision is None:
556
            rev_id = branch.last_revision()
557
        else:
558
            if len(revision) != 1:
559
                raise errors.BzrCommandError(
560
                    'bzr upload --revision takes exactly 1 argument')
561
            rev_id = revision[0].in_history(branch).rev_id
562
563
        tree = branch.repository.revision_tree(rev_id)
564
565
        uploader = BzrUploader(branch, to_transport, self.outf, tree,
0.152.62 by Vincent Ladeuil
Fix bug #423331 by adding a way to configure the path used to
566
                               rev_id, quiet=quiet)
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
567
568
        if full:
569
            uploader.upload_full_tree()
570
        else:
571
            uploader.upload_tree()
572
573
        # We uploaded successfully, remember it
574
        if get_upload_location(branch) is None or remember:
575
            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.
576
        if auto is not None:
577
            set_upload_auto(branch, auto)
0.155.1 by James Westby
Switch most of the logic to a class outside of the command class.
578
579
0.152.1 by Vincent Ladeuil
Empty shell
580
commands.register_command(cmd_upload)
581
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
582
def install_auto_upload_hook():
583
    from bzrlib.plugins.upload import auto_upload_hook
0.157.1 by James Westby
Don't try and register the hook if install_named_hook is not available
584
    branch.Branch.hooks.install_named_hook('post_change_branch_tip',
0.152.61 by Vincent Ladeuil
Fix bug #312686 and add an 'upload_auto_quiet' config variable.
585
            auto_upload_hook.auto_upload_hook,
0.157.1 by James Westby
Don't try and register the hook if install_named_hook is not available
586
            '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.
587
588
589
if hasattr(branch.Branch.hooks, "install_named_hook"):
590
    install_auto_upload_hook()
0.157.1 by James Westby
Don't try and register the hook if install_named_hook is not available
591
    auto_hook_available = True
592
else:
593
    auto_hook_available = False
0.155.2 by James Westby
Add a post_change_branch_tip hook to upload.
594
595
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
596
def load_tests(basic_tests, module, loader):
0.153.1 by Vincent Ladeuil
Clean up references to verbose.
597
    # 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.
598
    # that. I prefer to update basic_tests with the other tests to detect
599
    # unwanted tests and I think that's sufficient.
0.152.1 by Vincent Ladeuil
Empty shell
600
601
    testmod_names = [
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
602
        'tests',
0.152.1 by Vincent Ladeuil
Empty shell
603
        ]
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
604
    basic_tests.addTest(loader.loadTestsFromModuleNames(
0.152.1 by Vincent Ladeuil
Empty shell
605
            ["%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.
606
    return basic_tests