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