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