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