/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.152.6 by Vincent Ladeuil
Really use the transports and test against all targeted protocols.
1
# Copyright (C) 2008 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.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
19
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.
20
revision id. The uploaded working tree is not linked to any other bzr data.
21
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
22
The intended use is for web developers with no shell access on their web site
23
forced to used FTP or SFTP to upload thei site content.
24
25
Known limitations:
26
- Symlinks are ignored,
27
28
- chmod bits are not supported.
0.152.6 by Vincent Ladeuil
Really use the transports and test against all targeted protocols.
29
"""
0.152.1 by Vincent Ladeuil
Empty shell
30
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
31
# TODO: the chmod bits *can* be supported via the upload protocols
32
# (i.e. poorly), but since the web developers use these protocols to upload
33
# manually, it is expected that the associated web server is coherent with
34
# their presence/absence. In other words, if a web hosting provider requires
35
# chmod bits but don't provide an ftp server that support them, well, better
36
# find another provider ;-)
37
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
38
# TODO: can't upload with conflicts present (or even uncommited changes ? Or at
39
# a least a warning ?) . Something along the lines:
40
41
#            if len(self.tree.conflicts()) > 0:
42
#                raise ConflictsInTree
43
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
44
from bzrlib import (
45
    commands,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
46
    lazy_import,
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
47
    option,
48
    )
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
49
lazy_import.lazy_import(globals(), """
50
from bzrlib import (
51
    branch,
52
    errors,
53
    revisionspec,
54
    transport,
0.152.21 by Martin Albisetti
Fix uploading using the remembered location
55
    urlutils,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
56
    )
57
""")
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
58
0.152.23 by Martin Albisetti
Added version_info and plugin_name
59
version_info = (0,1,0)
60
plugin_name = 'upload'
61
0.152.1 by Vincent Ladeuil
Empty shell
62
class cmd_upload(commands.Command):
0.152.2 by v.ladeuil+lp at free
Add failing tests for basic usage.
63
    """Upload a working tree, as a whole or incrementally.
64
65
    If no destination is specified use the last one used.
66
    If no revision is specified upload the changes since the last upload.
67
    """
0.152.7 by Vincent Ladeuil
Slight refactoring.
68
    takes_args = ['location?']
0.152.2 by v.ladeuil+lp at free
Add failing tests for basic usage.
69
    takes_options = [
70
        'revision',
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
71
        'remember',
0.152.26 by Martin Albisetti
Added verbose output for the operations being done
72
        'verbose',
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
73
        option.Option('full', 'Upload the full working tree.'),
0.152.11 by Vincent Ladeuil
Be coherent with bzr push.
74
        option.Option('directory',
75
                      help='Branch to upload from, '
76
                      'rather than the one containing the working directory.',
77
                      short_name='d',
78
                      type=unicode,
79
                      ),
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
80
       ]
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
81
0.152.21 by Martin Albisetti
Fix uploading using the remembered location
82
    def run(self, location=None, full=False, revision=None, remember=None,
0.152.26 by Martin Albisetti
Added verbose output for the operations being done
83
            directory=None, verbose=False,
0.152.8 by Vincent Ladeuil
Handle uploading directories.
84
            ):
0.152.11 by Vincent Ladeuil
Be coherent with bzr push.
85
        if directory is None:
86
            directory = u'.'
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
87
        self.branch = branch.Branch.open_containing(directory)[0]
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
88
0.152.7 by Vincent Ladeuil
Slight refactoring.
89
        if location is None:
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
90
            stored_loc = self.get_upload_location()
91
            if stored_loc is None:
92
                raise errors.BzrCommandError('No upload location'
93
                                             ' known or specified.')
94
            else:
95
                display_url = urlutils.unescape_for_display(stored_loc,
96
                        self.outf.encoding)
97
                self.outf.write("Using saved location: %s\n" % display_url)
98
                location = stored_loc
99
100
        self.to_transport = transport.get_transport(location)
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
101
        if revision is None:
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
102
            rev_id = self.branch.last_revision()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
103
        else:
104
            if len(revision) != 1:
105
                raise errors.BzrCommandError(
106
                    'bzr upload --revision takes exactly 1 argument')
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
107
            rev_id = revision[0].in_history(self.branch).rev_id
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
108
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
109
        self.tree = self.branch.repository.revision_tree(rev_id)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
110
        self.rev_id = rev_id
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
111
        self._pending_renames = []
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
112
        self._pending_deletions = []
0.152.26 by Martin Albisetti
Added verbose output for the operations being done
113
        self.verbose = verbose
0.152.22 by Martin Albisetti
Changed default behaviour to do a full upload if no previous upload has been detected
114
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
115
        if full:
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
116
            self.upload_full_tree()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
117
        else:
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
118
            self.upload_tree()
119
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
120
        # We uploaded successfully, remember it
121
        if self.get_upload_location() is None or remember:
122
            self.set_upload_location(self.to_transport.base)
123
124
    def set_upload_location(self, location):
125
        self.branch.get_config().set_user_option('upload_location', location)
126
127
    def get_upload_location(self):
128
        return self.branch.get_config().get_user_option('upload_location')
129
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
130
    bzr_upload_revid_file_name = '.bzr-upload.revid'
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
131
132
    def set_uploaded_revid(self, rev_id):
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
133
        # XXX: Add tests for concurrent updates, etc.
134
        self.to_transport.put_bytes(self.bzr_upload_revid_file_name, rev_id)
135
136
    def get_uploaded_revid(self):
137
        return self.to_transport.get_bytes(self.bzr_upload_revid_file_name)
0.152.7 by Vincent Ladeuil
Slight refactoring.
138
139
    def upload_file(self, relpath, id):
0.152.26 by Martin Albisetti
Added verbose output for the operations being done
140
        if self.verbose:
141
            print 'Uploading: ' + relpath
0.152.7 by Vincent Ladeuil
Slight refactoring.
142
        self.to_transport.put_bytes(relpath, self.tree.get_file_text(id))
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
143
0.152.8 by Vincent Ladeuil
Handle uploading directories.
144
    def make_remote_dir(self, relpath):
145
        # XXX: handle mode
146
        self.to_transport.mkdir(relpath)
147
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
148
    def delete_remote_file(self, relpath):
0.152.26 by Martin Albisetti
Added verbose output for the operations being done
149
        if self.verbose:
150
            print 'Deleting: ' + relpath
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
151
        self.to_transport.delete(relpath)
152
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
153
    def delete_remote_dir(self, relpath):
0.152.26 by Martin Albisetti
Added verbose output for the operations being done
154
        if self.verbose:
155
            print 'Deleting: ' + relpath
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
156
        self.to_transport.rmdir(relpath)
157
158
    def delete_remote_dir_maybe(self, relpath):
159
        """Try to delete relpath, keeping failures to retry later."""
160
        try:
161
            self.to_transport.rmdir(relpath)
162
        # any kind of PathError would be OK, though we normally expect
163
        # DirectoryNotEmpty
164
        except errors.PathError:
165
            self._pending_deletions.append(relpath)
166
167
    def finish_deletions(self):
168
        if self._pending_deletions:
169
            # Process the previously failed deletions in reverse order to
170
            # delete children before parents
171
            for relpath in reversed(self._pending_deletions):
172
                self.to_transport.rmdir(relpath)
173
            # The following shouldn't be needed since we use it once per
174
            # upload, but better safe than sorry ;-)
175
            self._pending_deletions = []
176
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
177
    def rename_remote(self, old_relpath, new_relpath):
178
        """Rename a remote file or directory taking care of collisions.
179
180
        To avoid collisions during bulk renames, each renamed target is
181
        temporarily assigned a unique name. When all renames have been done,
182
        each target get its proper name.
183
        """
184
        # We generate a sufficiently random name to *assume* that
185
        # no collisions will occur and don't worry about it (nor
186
        # handle it).
187
        import os
188
        import random
189
        import time
190
191
        stamp = '.tmp.%.9f.%d.%d' % (time.time(),
192
                                     os.getpid(),
193
                                     random.randint(0,0x7FFFFFFF))
0.152.26 by Martin Albisetti
Added verbose output for the operations being done
194
        if self.verbose:
195
            print 'Renaming ' + old_relpath + ' to ' + new_relpath
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
196
        self.to_transport.rename(old_relpath, stamp)
197
        self._pending_renames.append((stamp, new_relpath))
198
199
    def finish_renames(self):
200
        for (stamp, new_path) in self._pending_renames:
201
            self.to_transport.rename(stamp, new_path)
202
        # The following shouldn't be needed since we use it once per upload,
203
        # but better safe than sorry ;-)
204
        self._pending_renames = []
205
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
206
    def upload_full_tree(self):
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
207
        self.to_transport.ensure_base() # XXX: Handle errors (add
208
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
209
        self.tree.lock_read()
210
        try:
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
211
            for dp, ie in self.tree.inventory.iter_entries():
212
                if dp in ('', '.bzrignore'):
213
                    # skip root ('')
214
                    # .bzrignore has no meaning outside of a working tree
215
                    # so do not upload it
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
216
                    continue
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
217
                # XXX: We need to be more robust in case we upload on top of an
218
                # existing tree which may contains existing files or dirs whose
219
                # names will make attempts to upload dirs or files fail.
0.152.8 by Vincent Ladeuil
Handle uploading directories.
220
                if ie.kind == 'file':
221
                    self.upload_file(dp, ie.file_id)
222
                elif ie.kind == 'directory':
223
                    try:
224
                        self.make_remote_dir(dp)
225
                    except errors.FileExists:
226
                        # The directory existed before the upload
227
                        pass
228
                else:
229
                    raise NotImplementedError
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
230
            self.set_uploaded_revid(self.rev_id)
231
        finally:
232
            self.tree.unlock()
233
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
234
    def upload_tree(self):
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
235
        # If we can't find the revid file on the remote location, upload the
236
        # full tree instead
237
        try:
238
            rev_id = self.get_uploaded_revid()
239
        except errors.NoSuchFile:
240
            self.upload_full_tree()
241
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
242
        # XXX: errors out if rev_id not in branch history (probably someone
243
        # uploaded from a different branch).
244
        from_tree = self.branch.repository.revision_tree(rev_id)
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
245
        self.to_transport.ensure_base() # XXX: Handle errors (add
246
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
247
        changes = self.tree.changes_from(from_tree)
248
        self.tree.lock_read()
249
        try:
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
250
            for (path, id, kind) in changes.removed:
251
                if kind is 'file':
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
252
                    self.delete_remote_file(path)
253
                elif kind is  'directory':
254
                    self.delete_remote_dir_maybe(path)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
255
                else:
256
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
257
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
258
            for (old_path, new_path, id, kind,
259
                 content_change, exec_change) in changes.renamed:
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
260
                self.rename_remote(old_path, new_path)
261
            self.finish_renames()
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
262
            self.finish_deletions()
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
263
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
264
            for (path, id, old_kind, new_kind) in changes.kind_changed:
265
                if old_kind is 'file':
266
                    self.delete_remote_file(path)
267
                elif old_kind is  'directory':
268
                    self.delete_remote_dir(path)
269
                else:
270
                    raise NotImplementedError
271
272
                if new_kind is 'file':
273
                    self.upload_file(path, id)
274
                elif new_kind is 'directory':
275
                    self.make_remote_dir(path)
276
                else:
277
                    raise NotImplementedError
278
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
279
            for (path, id, kind) in changes.added:
280
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
281
                    self.upload_file(path, id)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
282
                elif kind is 'directory':
283
                    self.make_remote_dir(path)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
284
                else:
285
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
286
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
287
            # XXX: Add a test for exec_change
288
            for (path, id, kind,
289
                 content_change, exec_change) in changes.modified:
290
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
291
                    self.upload_file(path, id)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
292
                else:
293
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
294
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
295
            self.set_uploaded_revid(self.rev_id)
296
        finally:
297
            self.tree.unlock()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
298
0.152.1 by Vincent Ladeuil
Empty shell
299
300
commands.register_command(cmd_upload)
301
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
302
0.152.1 by Vincent Ladeuil
Empty shell
303
def test_suite():
304
    from bzrlib.tests import TestUtil
305
306
    suite = TestUtil.TestSuite()
307
    loader = TestUtil.TestLoader()
308
    testmod_names = [
309
        'test_upload',
310
        ]
311
312
    suite.addTest(loader.loadTestsFromModuleNames(
313
            ["%s.%s" % (__name__, tmn) for tmn in testmod_names]))
314
    return suite