/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.3 by v.ladeuil+lp at free
Make the tests fail not error out.
72
        option.Option('full', 'Upload the full working tree.'),
0.152.11 by Vincent Ladeuil
Be coherent with bzr push.
73
        option.Option('directory',
74
                      help='Branch to upload from, '
75
                      'rather than the one containing the working directory.',
76
                      short_name='d',
77
                      type=unicode,
78
                      ),
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
79
       ]
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
80
0.152.21 by Martin Albisetti
Fix uploading using the remembered location
81
    def run(self, location=None, full=False, revision=None, remember=None,
0.152.11 by Vincent Ladeuil
Be coherent with bzr push.
82
            directory=None,
0.152.8 by Vincent Ladeuil
Handle uploading directories.
83
            ):
0.152.11 by Vincent Ladeuil
Be coherent with bzr push.
84
        if directory is None:
85
            directory = u'.'
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
86
        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.
87
0.152.7 by Vincent Ladeuil
Slight refactoring.
88
        if location is None:
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
89
            stored_loc = self.get_upload_location()
90
            if stored_loc is None:
91
                raise errors.BzrCommandError('No upload location'
92
                                             ' known or specified.')
93
            else:
94
                display_url = urlutils.unescape_for_display(stored_loc,
95
                        self.outf.encoding)
96
                self.outf.write("Using saved location: %s\n" % display_url)
97
                location = stored_loc
98
99
        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.
100
        if revision is None:
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
101
            rev_id = self.branch.last_revision()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
102
        else:
103
            if len(revision) != 1:
104
                raise errors.BzrCommandError(
105
                    'bzr upload --revision takes exactly 1 argument')
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
106
            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.
107
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
108
        self.tree = self.branch.repository.revision_tree(rev_id)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
109
        self.rev_id = rev_id
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
110
        self._pending_renames = []
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
111
        self._pending_deletions = []
0.152.22 by Martin Albisetti
Changed default behaviour to do a full upload if no previous upload has been detected
112
113
        # We check to see if we have previously uploaded, if not
114
        # we do a full initial upload. I believe this should be
115
        # the default behaviour
116
        try:
117
            rev_id = self.get_uploaded_revid()
118
        except errors.PathError:
119
            full = True
120
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
121
        if full:
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
122
            self.upload_full_tree()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
123
        else:
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
124
            self.upload_tree()
125
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
126
        # We uploaded successfully, remember it
127
        if self.get_upload_location() is None or remember:
128
            self.set_upload_location(self.to_transport.base)
129
130
    def set_upload_location(self, location):
131
        self.branch.get_config().set_user_option('upload_location', location)
132
133
    def get_upload_location(self):
134
        return self.branch.get_config().get_user_option('upload_location')
135
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
136
    bzr_upload_revid_file_name = '.bzr-upload.revid'
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
137
138
    def set_uploaded_revid(self, rev_id):
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
139
        # XXX: Add tests for concurrent updates, etc.
140
        self.to_transport.put_bytes(self.bzr_upload_revid_file_name, rev_id)
141
142
    def get_uploaded_revid(self):
143
        return self.to_transport.get_bytes(self.bzr_upload_revid_file_name)
0.152.7 by Vincent Ladeuil
Slight refactoring.
144
145
    def upload_file(self, relpath, id):
146
        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.
147
0.152.8 by Vincent Ladeuil
Handle uploading directories.
148
    def make_remote_dir(self, relpath):
149
        # XXX: handle mode
150
        self.to_transport.mkdir(relpath)
151
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
152
    def delete_remote_file(self, relpath):
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
153
        self.to_transport.delete(relpath)
154
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
155
    def delete_remote_dir(self, relpath):
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))
194
        self.to_transport.rename(old_relpath, stamp)
195
        self._pending_renames.append((stamp, new_relpath))
196
197
    def finish_renames(self):
198
        for (stamp, new_path) in self._pending_renames:
199
            self.to_transport.rename(stamp, new_path)
200
        # The following shouldn't be needed since we use it once per upload,
201
        # but better safe than sorry ;-)
202
        self._pending_renames = []
203
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
204
    def upload_full_tree(self):
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
205
        self.to_transport.ensure_base() # XXX: Handle errors (add
206
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
207
        self.tree.lock_read()
208
        try:
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
209
            for dp, ie in self.tree.inventory.iter_entries():
210
                if dp in ('', '.bzrignore'):
211
                    # skip root ('')
212
                    # .bzrignore has no meaning outside of a working tree
213
                    # so do not upload it
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
214
                    continue
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
215
                # XXX: We need to be more robust in case we upload on top of an
216
                # existing tree which may contains existing files or dirs whose
217
                # names will make attempts to upload dirs or files fail.
0.152.8 by Vincent Ladeuil
Handle uploading directories.
218
                if ie.kind == 'file':
219
                    self.upload_file(dp, ie.file_id)
220
                elif ie.kind == 'directory':
221
                    try:
222
                        self.make_remote_dir(dp)
223
                    except errors.FileExists:
224
                        # The directory existed before the upload
225
                        pass
226
                else:
227
                    raise NotImplementedError
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
228
            self.set_uploaded_revid(self.rev_id)
229
        finally:
230
            self.tree.unlock()
231
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
232
    def upload_tree(self):
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
233
        # XXX: if we get NoSuchFile should we consider it the first upload ever
234
        # and upload changes since the zeroth revision ?  Add tests.
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
235
        rev_id = self.get_uploaded_revid()
236
        # XXX: errors out if rev_id not in branch history (probably someone
237
        # uploaded from a different branch).
238
        from_tree = self.branch.repository.revision_tree(rev_id)
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
239
        self.to_transport.ensure_base() # XXX: Handle errors (add
240
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
241
        changes = self.tree.changes_from(from_tree)
242
        self.tree.lock_read()
243
        try:
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
244
            for (path, id, kind) in changes.removed:
245
                if kind is 'file':
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
246
                    self.delete_remote_file(path)
247
                elif kind is  'directory':
248
                    self.delete_remote_dir_maybe(path)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
249
                else:
250
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
251
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
252
            for (old_path, new_path, id, kind,
253
                 content_change, exec_change) in changes.renamed:
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
254
                self.rename_remote(old_path, new_path)
255
            self.finish_renames()
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
256
            self.finish_deletions()
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
257
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
258
            for (path, id, old_kind, new_kind) in changes.kind_changed:
259
                if old_kind is 'file':
260
                    self.delete_remote_file(path)
261
                elif old_kind is  'directory':
262
                    self.delete_remote_dir(path)
263
                else:
264
                    raise NotImplementedError
265
266
                if new_kind is 'file':
267
                    self.upload_file(path, id)
268
                elif new_kind is 'directory':
269
                    self.make_remote_dir(path)
270
                else:
271
                    raise NotImplementedError
272
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
273
            for (path, id, kind) in changes.added:
274
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
275
                    self.upload_file(path, id)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
276
                elif kind is 'directory':
277
                    self.make_remote_dir(path)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
278
                else:
279
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
280
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
281
            # XXX: Add a test for exec_change
282
            for (path, id, kind,
283
                 content_change, exec_change) in changes.modified:
284
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
285
                    self.upload_file(path, id)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
286
                else:
287
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
288
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
289
            self.set_uploaded_revid(self.rev_id)
290
        finally:
291
            self.tree.unlock()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
292
0.152.1 by Vincent Ladeuil
Empty shell
293
294
commands.register_command(cmd_upload)
295
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
296
0.152.1 by Vincent Ladeuil
Empty shell
297
def test_suite():
298
    from bzrlib.tests import TestUtil
299
300
    suite = TestUtil.TestSuite()
301
    loader = TestUtil.TestLoader()
302
    testmod_names = [
303
        'test_upload',
304
        ]
305
306
    suite.addTest(loader.loadTestsFromModuleNames(
307
            ["%s.%s" % (__name__, tmn) for tmn in testmod_names]))
308
    return suite