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