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