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