~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/branch.py

  • Committer: Martin Packman
  • Date: 2011-11-17 13:45:49 UTC
  • mto: This revision was merged to the branch mainline in revision 6271.
  • Revision ID: martin.packman@canonical.com-20111117134549-080e1fhtrzoicexg
Only assert FileExists path in test_transform directory clash tests to avoid stringification fallout

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2010 Canonical Ltd
 
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Server-side branch related request implmentations."""
 
18
 
 
19
 
 
20
from bzrlib import (
 
21
    bencode,
 
22
    errors,
 
23
    revision as _mod_revision,
 
24
    )
 
25
from bzrlib.controldir import ControlDir
 
26
from bzrlib.smart.request import (
 
27
    FailedSmartServerResponse,
 
28
    SmartServerRequest,
 
29
    SuccessfulSmartServerResponse,
 
30
    )
 
31
 
 
32
 
 
33
class SmartServerBranchRequest(SmartServerRequest):
 
34
    """Base class for handling common branch request logic.
 
35
    """
 
36
 
 
37
    def do(self, path, *args):
 
38
        """Execute a request for a branch at path.
 
39
 
 
40
        All Branch requests take a path to the branch as their first argument.
 
41
 
 
42
        If the branch is a branch reference, NotBranchError is raised.
 
43
 
 
44
        :param path: The path for the repository as received from the
 
45
            client.
 
46
        :return: A SmartServerResponse from self.do_with_branch().
 
47
        """
 
48
        transport = self.transport_from_client_path(path)
 
49
        controldir = ControlDir.open_from_transport(transport)
 
50
        if controldir.get_branch_reference() is not None:
 
51
            raise errors.NotBranchError(transport.base)
 
52
        branch = controldir.open_branch(ignore_fallbacks=True)
 
53
        return self.do_with_branch(branch, *args)
 
54
 
 
55
 
 
56
class SmartServerLockedBranchRequest(SmartServerBranchRequest):
 
57
    """Base class for handling common branch request logic for requests that
 
58
    need a write lock.
 
59
    """
 
60
 
 
61
    def do_with_branch(self, branch, branch_token, repo_token, *args):
 
62
        """Execute a request for a branch.
 
63
 
 
64
        A write lock will be acquired with the given tokens for the branch and
 
65
        repository locks.  The lock will be released once the request is
 
66
        processed.  The physical lock state won't be changed.
 
67
        """
 
68
        # XXX: write a test for LockContention
 
69
        branch.repository.lock_write(token=repo_token)
 
70
        try:
 
71
            branch.lock_write(token=branch_token)
 
72
            try:
 
73
                return self.do_with_locked_branch(branch, *args)
 
74
            finally:
 
75
                branch.unlock()
 
76
        finally:
 
77
            branch.repository.unlock()
 
78
 
 
79
 
 
80
class SmartServerBranchGetConfigFile(SmartServerBranchRequest):
 
81
 
 
82
    def do_with_branch(self, branch):
 
83
        """Return the content of branch.conf
 
84
 
 
85
        The body is not utf8 decoded - its the literal bytestream from disk.
 
86
        """
 
87
        try:
 
88
            content = branch._transport.get_bytes('branch.conf')
 
89
        except errors.NoSuchFile:
 
90
            content = ''
 
91
        return SuccessfulSmartServerResponse( ('ok', ), content)
 
92
 
 
93
 
 
94
class SmartServerBranchGetParent(SmartServerBranchRequest):
 
95
 
 
96
    def do_with_branch(self, branch):
 
97
        """Return the parent of branch."""
 
98
        parent = branch._get_parent_location() or ''
 
99
        return SuccessfulSmartServerResponse((parent,))
 
100
 
 
101
 
 
102
class SmartServerBranchGetTagsBytes(SmartServerBranchRequest):
 
103
 
 
104
    def do_with_branch(self, branch):
 
105
        """Return the _get_tags_bytes for a branch."""
 
106
        bytes = branch._get_tags_bytes()
 
107
        return SuccessfulSmartServerResponse((bytes,))
 
108
 
 
109
 
 
110
class SmartServerBranchSetTagsBytes(SmartServerLockedBranchRequest):
 
111
 
 
112
    def __init__(self, backing_transport, root_client_path='/', jail_root=None):
 
113
        SmartServerLockedBranchRequest.__init__(
 
114
            self, backing_transport, root_client_path, jail_root)
 
115
        self.locked = False
 
116
        
 
117
    def do_with_locked_branch(self, branch):
 
118
        """Call _set_tags_bytes for a branch.
 
119
 
 
120
        New in 1.18.
 
121
        """
 
122
        # We need to keep this branch locked until we get a body with the tags
 
123
        # bytes.
 
124
        self.branch = branch
 
125
        self.branch.lock_write()
 
126
        self.locked = True
 
127
 
 
128
    def do_body(self, bytes):
 
129
        self.branch._set_tags_bytes(bytes)
 
130
        return SuccessfulSmartServerResponse(())
 
131
 
 
132
    def do_end(self):
 
133
        # TODO: this request shouldn't have to do this housekeeping manually.
 
134
        # Some of this logic probably belongs in a base class.
 
135
        if not self.locked:
 
136
            # We never acquired the branch successfully in the first place, so
 
137
            # there's nothing more to do.
 
138
            return
 
139
        try:
 
140
            return SmartServerLockedBranchRequest.do_end(self)
 
141
        finally:
 
142
            # Only try unlocking if we locked successfully in the first place
 
143
            self.branch.unlock()
 
144
 
 
145
 
 
146
class SmartServerBranchHeadsToFetch(SmartServerBranchRequest):
 
147
 
 
148
    def do_with_branch(self, branch):
 
149
        """Return the heads-to-fetch for a Branch as two bencoded lists.
 
150
        
 
151
        See Branch.heads_to_fetch.
 
152
 
 
153
        New in 2.4.
 
154
        """
 
155
        must_fetch, if_present_fetch = branch.heads_to_fetch()
 
156
        return SuccessfulSmartServerResponse(
 
157
            (list(must_fetch), list(if_present_fetch)))
 
158
 
 
159
 
 
160
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
 
161
 
 
162
    def do_with_branch(self, branch):
 
163
        stacked_on_url = branch.get_stacked_on_url()
 
164
        return SuccessfulSmartServerResponse(('ok', stacked_on_url))
 
165
 
 
166
 
 
167
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
 
168
 
 
169
    def do_with_branch(self, branch):
 
170
        """Get the revision history for the branch.
 
171
 
 
172
        The revision list is returned as the body content,
 
173
        with each revision utf8 encoded and \x00 joined.
 
174
        """
 
175
        branch.lock_read()
 
176
        try:
 
177
            graph = branch.repository.get_graph()
 
178
            stop_revisions = (None, _mod_revision.NULL_REVISION)
 
179
            history = list(graph.iter_lefthand_ancestry(
 
180
                branch.last_revision(), stop_revisions))
 
181
        finally:
 
182
            branch.unlock()
 
183
        return SuccessfulSmartServerResponse(
 
184
            ('ok', ), ('\x00'.join(reversed(history))))
 
185
 
 
186
 
 
187
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
 
188
 
 
189
    def do_with_branch(self, branch):
 
190
        """Return branch.last_revision_info().
 
191
 
 
192
        The revno is encoded in decimal, the revision_id is encoded as utf8.
 
193
        """
 
194
        revno, last_revision = branch.last_revision_info()
 
195
        return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
 
196
 
 
197
 
 
198
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
 
199
    """Base class for handling common branch request logic for requests that
 
200
    update the branch tip.
 
201
    """
 
202
 
 
203
    def do_with_locked_branch(self, branch, *args):
 
204
        try:
 
205
            return self.do_tip_change_with_locked_branch(branch, *args)
 
206
        except errors.TipChangeRejected, e:
 
207
            msg = e.msg
 
208
            if isinstance(msg, unicode):
 
209
                msg = msg.encode('utf-8')
 
210
            return FailedSmartServerResponse(('TipChangeRejected', msg))
 
211
 
 
212
 
 
213
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
 
214
    """Set an option in the branch configuration."""
 
215
 
 
216
    def do_with_locked_branch(self, branch, value, name, section):
 
217
        if not section:
 
218
            section = None
 
219
        branch._get_config().set_option(value.decode('utf8'), name, section)
 
220
        return SuccessfulSmartServerResponse(())
 
221
 
 
222
 
 
223
class SmartServerBranchRequestSetConfigOptionDict(SmartServerLockedBranchRequest):
 
224
    """Set an option in the branch configuration.
 
225
    
 
226
    New in 2.2.
 
227
    """
 
228
 
 
229
    def do_with_locked_branch(self, branch, value_dict, name, section):
 
230
        utf8_dict = bencode.bdecode(value_dict)
 
231
        value_dict = {}
 
232
        for key, value in utf8_dict.items():
 
233
            value_dict[key.decode('utf8')] = value.decode('utf8')
 
234
        if not section:
 
235
            section = None
 
236
        branch._get_config().set_option(value_dict, name, section)
 
237
        return SuccessfulSmartServerResponse(())
 
238
 
 
239
 
 
240
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
 
241
 
 
242
    def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
 
243
        if new_last_revision_id == 'null:':
 
244
            branch._set_revision_history([])
 
245
        else:
 
246
            if not branch.repository.has_revision(new_last_revision_id):
 
247
                return FailedSmartServerResponse(
 
248
                    ('NoSuchRevision', new_last_revision_id))
 
249
            branch._set_revision_history(branch._lefthand_history(
 
250
                new_last_revision_id, None, None))
 
251
        return SuccessfulSmartServerResponse(('ok',))
 
252
 
 
253
 
 
254
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
 
255
 
 
256
    def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
 
257
            allow_divergence, allow_overwrite_descendant):
 
258
        """Set the last revision of the branch.
 
259
 
 
260
        New in 1.6.
 
261
 
 
262
        :param new_last_revision_id: the revision ID to set as the last
 
263
            revision of the branch.
 
264
        :param allow_divergence: A flag.  If non-zero, change the revision ID
 
265
            even if the new_last_revision_id's ancestry has diverged from the
 
266
            current last revision.  If zero, a 'Diverged' error will be
 
267
            returned if new_last_revision_id is not a descendant of the current
 
268
            last revision.
 
269
        :param allow_overwrite_descendant:  A flag.  If zero and
 
270
            new_last_revision_id is not a descendant of the current last
 
271
            revision, then the last revision will not be changed.  If non-zero
 
272
            and there is no divergence, then the last revision is always
 
273
            changed.
 
274
 
 
275
        :returns: on success, a tuple of ('ok', revno, revision_id), where
 
276
            revno and revision_id are the new values of the current last
 
277
            revision info.  The revision_id might be different to the
 
278
            new_last_revision_id if allow_overwrite_descendant was not set.
 
279
        """
 
280
        do_not_overwrite_descendant = not allow_overwrite_descendant
 
281
        try:
 
282
            last_revno, last_rev = branch.last_revision_info()
 
283
            graph = branch.repository.get_graph()
 
284
            if not allow_divergence or do_not_overwrite_descendant:
 
285
                relation = branch._revision_relations(
 
286
                    last_rev, new_last_revision_id, graph)
 
287
                if relation == 'diverged' and not allow_divergence:
 
288
                    return FailedSmartServerResponse(('Diverged',))
 
289
                if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
 
290
                    return SuccessfulSmartServerResponse(
 
291
                        ('ok', last_revno, last_rev))
 
292
            new_revno = graph.find_distance_to_null(
 
293
                new_last_revision_id, [(last_rev, last_revno)])
 
294
            branch.set_last_revision_info(new_revno, new_last_revision_id)
 
295
        except errors.GhostRevisionsHaveNoRevno:
 
296
            return FailedSmartServerResponse(
 
297
                ('NoSuchRevision', new_last_revision_id))
 
298
        return SuccessfulSmartServerResponse(
 
299
            ('ok', new_revno, new_last_revision_id))
 
300
 
 
301
 
 
302
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
 
303
    """Branch.set_last_revision_info.  Sets the revno and the revision ID of
 
304
    the specified branch.
 
305
 
 
306
    New in bzrlib 1.4.
 
307
    """
 
308
 
 
309
    def do_tip_change_with_locked_branch(self, branch, new_revno,
 
310
            new_last_revision_id):
 
311
        try:
 
312
            branch.set_last_revision_info(int(new_revno), new_last_revision_id)
 
313
        except errors.NoSuchRevision:
 
314
            return FailedSmartServerResponse(
 
315
                ('NoSuchRevision', new_last_revision_id))
 
316
        return SuccessfulSmartServerResponse(('ok',))
 
317
 
 
318
 
 
319
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
 
320
    """Set the parent location for a branch.
 
321
    
 
322
    Takes a location to set, which must be utf8 encoded.
 
323
    """
 
324
 
 
325
    def do_with_locked_branch(self, branch, location):
 
326
        branch._set_parent_location(location)
 
327
        return SuccessfulSmartServerResponse(())
 
328
 
 
329
 
 
330
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
 
331
 
 
332
    def do_with_branch(self, branch, branch_token='', repo_token=''):
 
333
        if branch_token == '':
 
334
            branch_token = None
 
335
        if repo_token == '':
 
336
            repo_token = None
 
337
        try:
 
338
            repo_token = branch.repository.lock_write(
 
339
                token=repo_token).repository_token
 
340
            try:
 
341
                branch_token = branch.lock_write(
 
342
                    token=branch_token).branch_token
 
343
            finally:
 
344
                # this leaves the repository with 1 lock
 
345
                branch.repository.unlock()
 
346
        except errors.LockContention:
 
347
            return FailedSmartServerResponse(('LockContention',))
 
348
        except errors.TokenMismatch:
 
349
            return FailedSmartServerResponse(('TokenMismatch',))
 
350
        except errors.UnlockableTransport:
 
351
            return FailedSmartServerResponse(('UnlockableTransport',))
 
352
        except errors.LockFailed, e:
 
353
            return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
 
354
        if repo_token is None:
 
355
            repo_token = ''
 
356
        else:
 
357
            branch.repository.leave_lock_in_place()
 
358
        branch.leave_lock_in_place()
 
359
        branch.unlock()
 
360
        return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
 
361
 
 
362
 
 
363
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
 
364
 
 
365
    def do_with_branch(self, branch, branch_token, repo_token):
 
366
        try:
 
367
            branch.repository.lock_write(token=repo_token)
 
368
            try:
 
369
                branch.lock_write(token=branch_token)
 
370
            finally:
 
371
                branch.repository.unlock()
 
372
        except errors.TokenMismatch:
 
373
            return FailedSmartServerResponse(('TokenMismatch',))
 
374
        if repo_token:
 
375
            branch.repository.dont_leave_lock_in_place()
 
376
        branch.dont_leave_lock_in_place()
 
377
        branch.unlock()
 
378
        return SuccessfulSmartServerResponse(('ok',))
 
379