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