/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.35 by Martin Albisetti
* Tell the user if the remote location is already up to date
22
The intended use is for web developers which keep their web sites versioned
23
with bzr, can can use either FTP or SFTP to upload their site.
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
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,
47
    errors,
48
    revisionspec,
49
    transport,
0.152.27 by Martin Albisetti
* Added error message if the working tree has uncommited changes
50
    workingtree,
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
51
    )
52
""")
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
53
0.152.23 by Martin Albisetti
Added version_info and plugin_name
54
version_info = (0,1,0)
55
plugin_name = 'upload'
56
0.152.1 by Vincent Ladeuil
Empty shell
57
class cmd_upload(commands.Command):
0.152.2 by v.ladeuil+lp at free
Add failing tests for basic usage.
58
    """Upload a working tree, as a whole or incrementally.
59
60
    If no destination is specified use the last one used.
61
    If no revision is specified upload the changes since the last upload.
62
    """
0.152.7 by Vincent Ladeuil
Slight refactoring.
63
    takes_args = ['location?']
0.152.2 by v.ladeuil+lp at free
Add failing tests for basic usage.
64
    takes_options = [
65
        'revision',
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
66
        'remember',
0.152.3 by v.ladeuil+lp at free
Make the tests fail not error out.
67
        option.Option('full', 'Upload the full working tree.'),
0.152.35 by Martin Albisetti
* Tell the user if the remote location is already up to date
68
        option.Option('quiet', 'Do not output what is being done.', 
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
69
                       short_name='q'),
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.153.1 by Vincent Ladeuil
Clean up references to verbose.
79
            directory=None, quiet=False,
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.27 by Martin Albisetti
* Added error message if the working tree has uncommited changes
83
84
        wt = workingtree.WorkingTree.open(directory)
85
        changes = wt.changes_from(wt.basis_tree())
0.152.33 by Vincent Ladeuil
Ensure that we refuse to upload if the working tree contains
86
0.152.27 by Martin Albisetti
* Added error message if the working tree has uncommited changes
87
        if changes.has_changed():
88
            raise errors.UncommittedChanges(wt)
89
0.152.30 by Vincent Ladeuil
Comply to verbose.
90
        self.branch = wt.branch
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
91
0.152.7 by Vincent Ladeuil
Slight refactoring.
92
        if location is None:
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
93
            stored_loc = self.get_upload_location()
94
            if stored_loc is None:
95
                raise errors.BzrCommandError('No upload location'
96
                                             ' known or specified.')
97
            else:
98
                display_url = urlutils.unescape_for_display(stored_loc,
99
                        self.outf.encoding)
100
                self.outf.write("Using saved location: %s\n" % display_url)
101
                location = stored_loc
102
103
        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.
104
        if revision is None:
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
105
            rev_id = self.branch.last_revision()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
106
        else:
107
            if len(revision) != 1:
108
                raise errors.BzrCommandError(
109
                    'bzr upload --revision takes exactly 1 argument')
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
110
            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.
111
0.152.12 by Vincent Ladeuil
Implement 'upload_location' in config files.
112
        self.tree = self.branch.repository.revision_tree(rev_id)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
113
        self.rev_id = rev_id
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
114
        self._pending_renames = []
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
115
        self._pending_deletions = []
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
116
        self.quiet = quiet
0.152.22 by Martin Albisetti
Changed default behaviour to do a full upload if no previous upload has been detected
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):
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
143
        if not self.quiet:
144
            self.outf.write('Uploading %s\n' % relpath)
0.152.7 by Vincent Ladeuil
Slight refactoring.
145
        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.
146
0.152.8 by Vincent Ladeuil
Handle uploading directories.
147
    def make_remote_dir(self, relpath):
148
        # XXX: handle mode
149
        self.to_transport.mkdir(relpath)
150
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
151
    def delete_remote_file(self, relpath):
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
152
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
153
            self.outf.write('Deleting %s\n' % relpath)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
154
        self.to_transport.delete(relpath)
155
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
156
    def delete_remote_dir(self, relpath):
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
157
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
158
            self.outf.write('Deleting %s\n' % relpath)
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
159
        self.to_transport.rmdir(relpath)
160
161
    def delete_remote_dir_maybe(self, relpath):
162
        """Try to delete relpath, keeping failures to retry later."""
163
        try:
164
            self.to_transport.rmdir(relpath)
165
        # any kind of PathError would be OK, though we normally expect
166
        # DirectoryNotEmpty
167
        except errors.PathError:
168
            self._pending_deletions.append(relpath)
169
170
    def finish_deletions(self):
171
        if self._pending_deletions:
172
            # Process the previously failed deletions in reverse order to
173
            # delete children before parents
174
            for relpath in reversed(self._pending_deletions):
175
                self.to_transport.rmdir(relpath)
176
            # The following shouldn't be needed since we use it once per
177
            # upload, but better safe than sorry ;-)
178
            self._pending_deletions = []
179
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
180
    def rename_remote(self, old_relpath, new_relpath):
181
        """Rename a remote file or directory taking care of collisions.
182
183
        To avoid collisions during bulk renames, each renamed target is
184
        temporarily assigned a unique name. When all renames have been done,
185
        each target get its proper name.
186
        """
187
        # We generate a sufficiently random name to *assume* that
188
        # no collisions will occur and don't worry about it (nor
189
        # handle it).
190
        import os
191
        import random
192
        import time
193
194
        stamp = '.tmp.%.9f.%d.%d' % (time.time(),
195
                                     os.getpid(),
196
                                     random.randint(0,0x7FFFFFFF))
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
197
        if not self.quiet:
0.152.30 by Vincent Ladeuil
Comply to verbose.
198
            self.outf.write('Renaming %s to %s\n' % (old_relpath, new_relpath))
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
199
        self.to_transport.rename(old_relpath, stamp)
200
        self._pending_renames.append((stamp, new_relpath))
201
202
    def finish_renames(self):
203
        for (stamp, new_path) in self._pending_renames:
204
            self.to_transport.rename(stamp, new_path)
205
        # The following shouldn't be needed since we use it once per upload,
206
        # but better safe than sorry ;-)
207
        self._pending_renames = []
208
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
209
    def upload_full_tree(self):
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
210
        self.to_transport.ensure_base() # XXX: Handle errors (add
211
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
212
        self.tree.lock_read()
213
        try:
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
214
            for dp, ie in self.tree.inventory.iter_entries():
215
                if dp in ('', '.bzrignore'):
216
                    # skip root ('')
217
                    # .bzrignore has no meaning outside of a working tree
218
                    # so do not upload it
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
219
                    continue
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
220
                # XXX: We need to be more robust in case we upload on top of an
221
                # existing tree which may contains existing files or dirs whose
222
                # names will make attempts to upload dirs or files fail.
0.152.8 by Vincent Ladeuil
Handle uploading directories.
223
                if ie.kind == 'file':
224
                    self.upload_file(dp, ie.file_id)
225
                elif ie.kind == 'directory':
226
                    try:
227
                        self.make_remote_dir(dp)
228
                    except errors.FileExists:
229
                        # The directory existed before the upload
230
                        pass
231
                else:
232
                    raise NotImplementedError
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
233
            self.set_uploaded_revid(self.rev_id)
234
        finally:
235
            self.tree.unlock()
236
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
237
    def upload_tree(self):
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
238
        # If we can't find the revid file on the remote location, upload the
239
        # full tree instead
240
        try:
241
            rev_id = self.get_uploaded_revid()
242
        except errors.NoSuchFile:
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
243
            if not self.quiet:
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
244
                self.outf.write('No uploaded revision id found,'
0.152.34 by Martin Albisetti
* Change the default behaviour to be more verbose
245
                                ' switching to full upload\n')
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
246
            self.upload_full_tree()
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
247
            # We're done
248
            return
0.152.24 by Martin Albisetti
Changed the way we upload the full tree if its never been uploaded
249
0.152.35 by Martin Albisetti
* Tell the user if the remote location is already up to date
250
        # Check if the revision hasn't already been uploaded
251
        if rev_id == self.rev_id:
252
            if not self.quiet:
253
                self.outf.write('Remote location already up to date\n')
254
0.152.10 by Vincent Ladeuil
Fix incremental upload cheat.
255
        # XXX: errors out if rev_id not in branch history (probably someone
256
        # uploaded from a different branch).
257
        from_tree = self.branch.repository.revision_tree(rev_id)
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
258
        self.to_transport.ensure_base() # XXX: Handle errors (add
259
                                        # --create-prefix option ?)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
260
        changes = self.tree.changes_from(from_tree)
261
        self.tree.lock_read()
262
        try:
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
263
            for (path, id, kind) in changes.removed:
264
                if kind is 'file':
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
265
                    self.delete_remote_file(path)
266
                elif kind is  'directory':
267
                    self.delete_remote_dir_maybe(path)
0.152.17 by Vincent Ladeuil
Handle deletes (trivial implementation).
268
                else:
269
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
270
0.152.14 by Vincent Ladeuil
Handle renames (trivial implementation).
271
            for (old_path, new_path, id, kind,
272
                 content_change, exec_change) in changes.renamed:
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
273
                self.rename_remote(old_path, new_path)
274
            self.finish_renames()
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
275
            self.finish_deletions()
0.152.16 by Vincent Ladeuil
Handle renames. Robust implementation.
276
0.152.19 by Vincent Ladeuil
Handle kind_change. Trivial implementation, blocked by bug #205636.
277
            for (path, id, old_kind, new_kind) in changes.kind_changed:
278
                if old_kind is 'file':
279
                    self.delete_remote_file(path)
280
                elif old_kind is  'directory':
281
                    self.delete_remote_dir(path)
282
                else:
283
                    raise NotImplementedError
284
285
                if new_kind is 'file':
286
                    self.upload_file(path, id)
287
                elif new_kind is 'directory':
288
                    self.make_remote_dir(path)
289
                else:
290
                    raise NotImplementedError
291
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
292
            for (path, id, kind) in changes.added:
293
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
294
                    self.upload_file(path, id)
0.152.8 by Vincent Ladeuil
Handle uploading directories.
295
                elif kind is 'directory':
296
                    self.make_remote_dir(path)
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
            # XXX: Add a test for exec_change
301
            for (path, id, kind,
302
                 content_change, exec_change) in changes.modified:
303
                if kind is 'file':
0.152.7 by Vincent Ladeuil
Slight refactoring.
304
                    self.upload_file(path, id)
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
305
                else:
306
                    raise NotImplementedError
0.152.18 by Vincent Ladeuil
Handle deletions. Robust implementation.
307
0.152.5 by v.ladeuil+lp at free
Partial incremental upload implementationm tests pass.
308
            self.set_uploaded_revid(self.rev_id)
309
        finally:
310
            self.tree.unlock()
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
311
0.152.1 by Vincent Ladeuil
Empty shell
312
313
commands.register_command(cmd_upload)
314
0.152.4 by v.ladeuil+lp at free
Implement a trivial implementation to make one test pass.
315
0.152.29 by Vincent Ladeuil
Work around test fixture limitation regarding self.outf (cough). All tests passing again.
316
def load_tests(basic_tests, module, loader):
0.153.1 by Vincent Ladeuil
Clean up references to verbose.
317
    # This module shouldn't define any tests but I don't know how to report
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
318
    # that. I prefer to update basic_tests with the other tests to detect
319
    # unwanted tests and I think that's sufficient.
0.152.1 by Vincent Ladeuil
Empty shell
320
321
    testmod_names = [
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
322
        'tests',
0.152.1 by Vincent Ladeuil
Empty shell
323
        ]
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
324
    basic_tests.addTest(loader.loadTestsFromModuleNames(
0.152.1 by Vincent Ladeuil
Empty shell
325
            ["%s.%s" % (__name__, tmn) for tmn in testmod_names]))
0.154.1 by Vincent Ladeuil
Create a simple setup.py and rework tests modules accordingly.
326
    return basic_tests