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