/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
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
113
        if full:
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
114
            self.upload_full_tree()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
115
        else:
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
116
            self.upload_tree()
117
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
118
        # We uploaded successfully, remember it
119
        if self.get_upload_location() is None or remember:
120
            self.set_upload_location(self.to_transport.base)
121
122
    def set_upload_location(self, location):
123
        self.branch.get_config().set_user_option('upload_location', location)
124
125
    def get_upload_location(self):
126
        return self.branch.get_config().get_user_option('upload_location')
127
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
128
    bzr_upload_revid_file_name = '.bzr-upload.revid'
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
129
130
    def set_uploaded_revid(self, rev_id):
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
131
        # XXX: Add tests for concurrent updates, etc.
132
        self.to_transport.put_bytes(self.bzr_upload_revid_file_name, rev_id)
133
134
    def get_uploaded_revid(self):
135
        return self.to_transport.get_bytes(self.bzr_upload_revid_file_name)
0.152.7 by Vincent Ladeuil
Slight refactoring.
136
137
    def upload_file(self, relpath, id):
138
        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.
139
0.152.8 by Vincent Ladeuil
Handle uploading directories.
140
    def make_remote_dir(self, relpath):
141
        # XXX: handle mode
142
        self.to_transport.mkdir(relpath)
143
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
144
    def delete_remote_file(self, relpath):
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
145
        self.to_transport.delete(relpath)
146
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
147
    def delete_remote_dir(self, relpath):
148
        self.to_transport.rmdir(relpath)
149
150
    def delete_remote_dir_maybe(self, relpath):
151
        """Try to delete relpath, keeping failures to retry later."""
152
        try:
153
            self.to_transport.rmdir(relpath)
154
        # any kind of PathError would be OK, though we normally expect
155
        # DirectoryNotEmpty
156
        except errors.PathError:
157
            self._pending_deletions.append(relpath)
158
159
    def finish_deletions(self):
160
        if self._pending_deletions:
161
            # Process the previously failed deletions in reverse order to
162
            # delete children before parents
163
            for relpath in reversed(self._pending_deletions):
164
                self.to_transport.rmdir(relpath)
165
            # The following shouldn't be needed since we use it once per
166
            # upload, but better safe than sorry ;-)
167
            self._pending_deletions = []
168
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
169
    def rename_remote(self, old_relpath, new_relpath):
170
        """Rename a remote file or directory taking care of collisions.
171
172
        To avoid collisions during bulk renames, each renamed target is
173
        temporarily assigned a unique name. When all renames have been done,
174
        each target get its proper name.
175
        """
176
        # We generate a sufficiently random name to *assume* that
177
        # no collisions will occur and don't worry about it (nor
178
        # handle it).
179
        import os
180
        import random
181
        import time
182
183
        stamp = '.tmp.%.9f.%d.%d' % (time.time(),
184
                                     os.getpid(),
185
                                     random.randint(0,0x7FFFFFFF))
186
        self.to_transport.rename(old_relpath, stamp)
187
        self._pending_renames.append((stamp, new_relpath))
188
189
    def finish_renames(self):
190
        for (stamp, new_path) in self._pending_renames:
191
            self.to_transport.rename(stamp, new_path)
192
        # The following shouldn't be needed since we use it once per upload,
193
        # but better safe than sorry ;-)
194
        self._pending_renames = []
195
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
196
    def upload_full_tree(self):
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
197
        self.to_transport.ensure_base() # XXX: Handle errors (add
198
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
199
        self.tree.lock_read()
200
        try:
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
201
            for dp, ie in self.tree.inventory.iter_entries():
202
                if dp in ('', '.bzrignore'):
203
                    # skip root ('')
204
                    # .bzrignore has no meaning outside of a working tree
205
                    # so do not upload it
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
206
                    continue
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
207
                # XXX: We need to be more robust in case we upload on top of an
208
                # existing tree which may contains existing files or dirs whose
209
                # names will make attempts to upload dirs or files fail.
0.152.8 by Vincent Ladeuil
Handle uploading directories.
210
                if ie.kind == 'file':
211
                    self.upload_file(dp, ie.file_id)
212
                elif ie.kind == 'directory':
213
                    try:
214
                        self.make_remote_dir(dp)
215
                    except errors.FileExists:
216
                        # The directory existed before the upload
217
                        pass
218
                else:
219
                    raise NotImplementedError
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
220
            self.set_uploaded_revid(self.rev_id)
221
        finally:
222
            self.tree.unlock()
223
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
224
    def upload_tree(self):
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
225
        # If we can't find the revid file on the remote location, upload the
226
        # full tree instead
227
        try:
228
            rev_id = self.get_uploaded_revid()
229
        except errors.NoSuchFile:
230
            self.upload_full_tree()
231
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
232
        # XXX: errors out if rev_id not in branch history (probably someone
233
        # uploaded from a different branch).
234
        from_tree = self.branch.repository.revision_tree(rev_id)
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
235
        self.to_transport.ensure_base() # XXX: Handle errors (add
236
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
237
        changes = self.tree.changes_from(from_tree)
238
        self.tree.lock_read()
239
        try:
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
240
            for (path, id, kind) in changes.removed:
241
                if kind is 'file':
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
242
                    self.delete_remote_file(path)
243
                elif kind is  'directory':
244
                    self.delete_remote_dir_maybe(path)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
245
                else:
246
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
247
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
248
            for (old_path, new_path, id, kind,
249
                 content_change, exec_change) in changes.renamed:
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
250
                self.rename_remote(old_path, new_path)
251
            self.finish_renames()
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
252
            self.finish_deletions()
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
253
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
254
            for (path, id, old_kind, new_kind) in changes.kind_changed:
255
                if old_kind is 'file':
256
                    self.delete_remote_file(path)
257
                elif old_kind is  'directory':
258
                    self.delete_remote_dir(path)
259
                else:
260
                    raise NotImplementedError
261
262
                if new_kind is 'file':
263
                    self.upload_file(path, id)
264
                elif new_kind is 'directory':
265
                    self.make_remote_dir(path)
266
                else:
267
                    raise NotImplementedError
268
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
269
            for (path, id, kind) in changes.added:
270
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
271
                    self.upload_file(path, id)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
272
                elif kind is 'directory':
273
                    self.make_remote_dir(path)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
274
                else:
275
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
276
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
277
            # XXX: Add a test for exec_change
278
            for (path, id, kind,
279
                 content_change, exec_change) in changes.modified:
280
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
281
                    self.upload_file(path, id)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
282
                else:
283
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
284
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
285
            self.set_uploaded_revid(self.rev_id)
286
        finally:
287
            self.tree.unlock()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
288
0.152.1 by Vincent Ladeuil
Empty shell
289
290
commands.register_command(cmd_upload)
291
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
def test_suite():
294
    from bzrlib.tests import TestUtil
295
296
    suite = TestUtil.TestSuite()
297
    loader = TestUtil.TestLoader()
298
    testmod_names = [
299
        'test_upload',
300
        ]
301
302
    suite.addTest(loader.loadTestsFromModuleNames(
303
            ["%s.%s" % (__name__, tmn) for tmn in testmod_names]))
304
    return suite