/brz/remove-bazaar

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