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