~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/branch.py

  • Committer: Jelmer Vernooij
  • Date: 2011-11-30 20:02:16 UTC
  • mto: This revision was merged to the branch mainline in revision 6333.
  • Revision ID: jelmer@samba.org-20111130200216-aoju21pdl20d1gkd
Consistently pass tree path when exporting.

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 SmartServerBranchBreakLock(SmartServerBranchRequest):
 
81
 
 
82
    def do_with_branch(self, branch):
 
83
        """Break a branch lock.
 
84
        """
 
85
        branch.break_lock()
 
86
        return SuccessfulSmartServerResponse(('ok', ), )
 
87
 
 
88
 
 
89
class SmartServerBranchGetConfigFile(SmartServerBranchRequest):
 
90
 
 
91
    def do_with_branch(self, branch):
 
92
        """Return the content of branch.conf
 
93
 
 
94
        The body is not utf8 decoded - its the literal bytestream from disk.
 
95
        """
 
96
        try:
 
97
            content = branch.control_transport.get_bytes('branch.conf')
 
98
        except errors.NoSuchFile:
 
99
            content = ''
 
100
        return SuccessfulSmartServerResponse( ('ok', ), content)
 
101
 
 
102
 
 
103
class SmartServerBranchPutConfigFile(SmartServerBranchRequest):
 
104
    """Set the configuration data for a branch.
 
105
 
 
106
    New in 2.5.
 
107
    """
 
108
 
 
109
    def do_with_branch(self, branch, branch_token, repo_token):
 
110
        """Set the content of branch.conf.
 
111
 
 
112
        The body is not utf8 decoded - its the literal bytestream for disk.
 
113
        """
 
114
        self._branch = branch
 
115
        self._branch_token = branch_token
 
116
        self._repo_token = repo_token
 
117
        # Signal we want a body
 
118
        return None
 
119
 
 
120
    def do_body(self, body_bytes):
 
121
        self._branch.repository.lock_write(token=self._repo_token)
 
122
        try:
 
123
            self._branch.lock_write(token=self._branch_token)
 
124
            try:
 
125
                self._branch.control_transport.put_bytes(
 
126
                    'branch.conf', body_bytes)
 
127
            finally:
 
128
                self._branch.unlock()
 
129
        finally:
 
130
            self._branch.repository.unlock()
 
131
        return SuccessfulSmartServerResponse(('ok', ))
 
132
 
 
133
 
 
134
class SmartServerBranchGetParent(SmartServerBranchRequest):
 
135
 
 
136
    def do_with_branch(self, branch):
 
137
        """Return the parent of branch."""
 
138
        parent = branch._get_parent_location() or ''
 
139
        return SuccessfulSmartServerResponse((parent,))
 
140
 
 
141
 
 
142
class SmartServerBranchGetTagsBytes(SmartServerBranchRequest):
 
143
 
 
144
    def do_with_branch(self, branch):
 
145
        """Return the _get_tags_bytes for a branch."""
 
146
        bytes = branch._get_tags_bytes()
 
147
        return SuccessfulSmartServerResponse((bytes,))
 
148
 
 
149
 
 
150
class SmartServerBranchSetTagsBytes(SmartServerLockedBranchRequest):
 
151
 
 
152
    def __init__(self, backing_transport, root_client_path='/', jail_root=None):
 
153
        SmartServerLockedBranchRequest.__init__(
 
154
            self, backing_transport, root_client_path, jail_root)
 
155
        self.locked = False
 
156
        
 
157
    def do_with_locked_branch(self, branch):
 
158
        """Call _set_tags_bytes for a branch.
 
159
 
 
160
        New in 1.18.
 
161
        """
 
162
        # We need to keep this branch locked until we get a body with the tags
 
163
        # bytes.
 
164
        self.branch = branch
 
165
        self.branch.lock_write()
 
166
        self.locked = True
 
167
 
 
168
    def do_body(self, bytes):
 
169
        self.branch._set_tags_bytes(bytes)
 
170
        return SuccessfulSmartServerResponse(())
 
171
 
 
172
    def do_end(self):
 
173
        # TODO: this request shouldn't have to do this housekeeping manually.
 
174
        # Some of this logic probably belongs in a base class.
 
175
        if not self.locked:
 
176
            # We never acquired the branch successfully in the first place, so
 
177
            # there's nothing more to do.
 
178
            return
 
179
        try:
 
180
            return SmartServerLockedBranchRequest.do_end(self)
 
181
        finally:
 
182
            # Only try unlocking if we locked successfully in the first place
 
183
            self.branch.unlock()
 
184
 
 
185
 
 
186
class SmartServerBranchHeadsToFetch(SmartServerBranchRequest):
 
187
 
 
188
    def do_with_branch(self, branch):
 
189
        """Return the heads-to-fetch for a Branch as two bencoded lists.
 
190
        
 
191
        See Branch.heads_to_fetch.
 
192
 
 
193
        New in 2.4.
 
194
        """
 
195
        must_fetch, if_present_fetch = branch.heads_to_fetch()
 
196
        return SuccessfulSmartServerResponse(
 
197
            (list(must_fetch), list(if_present_fetch)))
 
198
 
 
199
 
 
200
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
 
201
 
 
202
    def do_with_branch(self, branch):
 
203
        stacked_on_url = branch.get_stacked_on_url()
 
204
        return SuccessfulSmartServerResponse(('ok', stacked_on_url))
 
205
 
 
206
 
 
207
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
 
208
 
 
209
    def do_with_branch(self, branch):
 
210
        """Get the revision history for the branch.
 
211
 
 
212
        The revision list is returned as the body content,
 
213
        with each revision utf8 encoded and \x00 joined.
 
214
        """
 
215
        branch.lock_read()
 
216
        try:
 
217
            graph = branch.repository.get_graph()
 
218
            stop_revisions = (None, _mod_revision.NULL_REVISION)
 
219
            history = list(graph.iter_lefthand_ancestry(
 
220
                branch.last_revision(), stop_revisions))
 
221
        finally:
 
222
            branch.unlock()
 
223
        return SuccessfulSmartServerResponse(
 
224
            ('ok', ), ('\x00'.join(reversed(history))))
 
225
 
 
226
 
 
227
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
 
228
 
 
229
    def do_with_branch(self, branch):
 
230
        """Return branch.last_revision_info().
 
231
 
 
232
        The revno is encoded in decimal, the revision_id is encoded as utf8.
 
233
        """
 
234
        revno, last_revision = branch.last_revision_info()
 
235
        return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
 
236
 
 
237
 
 
238
class SmartServerBranchRequestRevisionIdToRevno(SmartServerBranchRequest):
 
239
 
 
240
    def do_with_branch(self, branch, revid):
 
241
        """Return branch.revision_id_to_revno().
 
242
 
 
243
        New in 2.5.
 
244
 
 
245
        The revno is encoded in decimal, the revision_id is encoded as utf8.
 
246
        """
 
247
        try:
 
248
            dotted_revno = branch.revision_id_to_dotted_revno(revid)
 
249
        except errors.NoSuchRevision:
 
250
            return FailedSmartServerResponse(('NoSuchRevision', revid))
 
251
        return SuccessfulSmartServerResponse(
 
252
            ('ok', ) + tuple(map(str, dotted_revno)))
 
253
 
 
254
 
 
255
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
 
256
    """Base class for handling common branch request logic for requests that
 
257
    update the branch tip.
 
258
    """
 
259
 
 
260
    def do_with_locked_branch(self, branch, *args):
 
261
        try:
 
262
            return self.do_tip_change_with_locked_branch(branch, *args)
 
263
        except errors.TipChangeRejected, e:
 
264
            msg = e.msg
 
265
            if isinstance(msg, unicode):
 
266
                msg = msg.encode('utf-8')
 
267
            return FailedSmartServerResponse(('TipChangeRejected', msg))
 
268
 
 
269
 
 
270
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
 
271
    """Set an option in the branch configuration."""
 
272
 
 
273
    def do_with_locked_branch(self, branch, value, name, section):
 
274
        if not section:
 
275
            section = None
 
276
        branch._get_config().set_option(value.decode('utf8'), name, section)
 
277
        return SuccessfulSmartServerResponse(())
 
278
 
 
279
 
 
280
class SmartServerBranchRequestSetConfigOptionDict(SmartServerLockedBranchRequest):
 
281
    """Set an option in the branch configuration.
 
282
    
 
283
    New in 2.2.
 
284
    """
 
285
 
 
286
    def do_with_locked_branch(self, branch, value_dict, name, section):
 
287
        utf8_dict = bencode.bdecode(value_dict)
 
288
        value_dict = {}
 
289
        for key, value in utf8_dict.items():
 
290
            value_dict[key.decode('utf8')] = value.decode('utf8')
 
291
        if not section:
 
292
            section = None
 
293
        branch._get_config().set_option(value_dict, name, section)
 
294
        return SuccessfulSmartServerResponse(())
 
295
 
 
296
 
 
297
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
 
298
 
 
299
    def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
 
300
        if new_last_revision_id == 'null:':
 
301
            branch._set_revision_history([])
 
302
        else:
 
303
            if not branch.repository.has_revision(new_last_revision_id):
 
304
                return FailedSmartServerResponse(
 
305
                    ('NoSuchRevision', new_last_revision_id))
 
306
            branch._set_revision_history(branch._lefthand_history(
 
307
                new_last_revision_id, None, None))
 
308
        return SuccessfulSmartServerResponse(('ok',))
 
309
 
 
310
 
 
311
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
 
312
 
 
313
    def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
 
314
            allow_divergence, allow_overwrite_descendant):
 
315
        """Set the last revision of the branch.
 
316
 
 
317
        New in 1.6.
 
318
 
 
319
        :param new_last_revision_id: the revision ID to set as the last
 
320
            revision of the branch.
 
321
        :param allow_divergence: A flag.  If non-zero, change the revision ID
 
322
            even if the new_last_revision_id's ancestry has diverged from the
 
323
            current last revision.  If zero, a 'Diverged' error will be
 
324
            returned if new_last_revision_id is not a descendant of the current
 
325
            last revision.
 
326
        :param allow_overwrite_descendant:  A flag.  If zero and
 
327
            new_last_revision_id is not a descendant of the current last
 
328
            revision, then the last revision will not be changed.  If non-zero
 
329
            and there is no divergence, then the last revision is always
 
330
            changed.
 
331
 
 
332
        :returns: on success, a tuple of ('ok', revno, revision_id), where
 
333
            revno and revision_id are the new values of the current last
 
334
            revision info.  The revision_id might be different to the
 
335
            new_last_revision_id if allow_overwrite_descendant was not set.
 
336
        """
 
337
        do_not_overwrite_descendant = not allow_overwrite_descendant
 
338
        try:
 
339
            last_revno, last_rev = branch.last_revision_info()
 
340
            graph = branch.repository.get_graph()
 
341
            if not allow_divergence or do_not_overwrite_descendant:
 
342
                relation = branch._revision_relations(
 
343
                    last_rev, new_last_revision_id, graph)
 
344
                if relation == 'diverged' and not allow_divergence:
 
345
                    return FailedSmartServerResponse(('Diverged',))
 
346
                if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
 
347
                    return SuccessfulSmartServerResponse(
 
348
                        ('ok', last_revno, last_rev))
 
349
            new_revno = graph.find_distance_to_null(
 
350
                new_last_revision_id, [(last_rev, last_revno)])
 
351
            branch.set_last_revision_info(new_revno, new_last_revision_id)
 
352
        except errors.GhostRevisionsHaveNoRevno:
 
353
            return FailedSmartServerResponse(
 
354
                ('NoSuchRevision', new_last_revision_id))
 
355
        return SuccessfulSmartServerResponse(
 
356
            ('ok', new_revno, new_last_revision_id))
 
357
 
 
358
 
 
359
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
 
360
    """Branch.set_last_revision_info.  Sets the revno and the revision ID of
 
361
    the specified branch.
 
362
 
 
363
    New in bzrlib 1.4.
 
364
    """
 
365
 
 
366
    def do_tip_change_with_locked_branch(self, branch, new_revno,
 
367
            new_last_revision_id):
 
368
        try:
 
369
            branch.set_last_revision_info(int(new_revno), new_last_revision_id)
 
370
        except errors.NoSuchRevision:
 
371
            return FailedSmartServerResponse(
 
372
                ('NoSuchRevision', new_last_revision_id))
 
373
        return SuccessfulSmartServerResponse(('ok',))
 
374
 
 
375
 
 
376
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
 
377
    """Set the parent location for a branch.
 
378
    
 
379
    Takes a location to set, which must be utf8 encoded.
 
380
    """
 
381
 
 
382
    def do_with_locked_branch(self, branch, location):
 
383
        branch._set_parent_location(location)
 
384
        return SuccessfulSmartServerResponse(())
 
385
 
 
386
 
 
387
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
 
388
 
 
389
    def do_with_branch(self, branch, branch_token='', repo_token=''):
 
390
        if branch_token == '':
 
391
            branch_token = None
 
392
        if repo_token == '':
 
393
            repo_token = None
 
394
        try:
 
395
            repo_token = branch.repository.lock_write(
 
396
                token=repo_token).repository_token
 
397
            try:
 
398
                branch_token = branch.lock_write(
 
399
                    token=branch_token).branch_token
 
400
            finally:
 
401
                # this leaves the repository with 1 lock
 
402
                branch.repository.unlock()
 
403
        except errors.LockContention:
 
404
            return FailedSmartServerResponse(('LockContention',))
 
405
        except errors.TokenMismatch:
 
406
            return FailedSmartServerResponse(('TokenMismatch',))
 
407
        except errors.UnlockableTransport:
 
408
            return FailedSmartServerResponse(('UnlockableTransport',))
 
409
        except errors.LockFailed, e:
 
410
            return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
 
411
        if repo_token is None:
 
412
            repo_token = ''
 
413
        else:
 
414
            branch.repository.leave_lock_in_place()
 
415
        branch.leave_lock_in_place()
 
416
        branch.unlock()
 
417
        return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
 
418
 
 
419
 
 
420
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
 
421
 
 
422
    def do_with_branch(self, branch, branch_token, repo_token):
 
423
        try:
 
424
            branch.repository.lock_write(token=repo_token)
 
425
            try:
 
426
                branch.lock_write(token=branch_token)
 
427
            finally:
 
428
                branch.repository.unlock()
 
429
        except errors.TokenMismatch:
 
430
            return FailedSmartServerResponse(('TokenMismatch',))
 
431
        if repo_token:
 
432
            branch.repository.dont_leave_lock_in_place()
 
433
        branch.dont_leave_lock_in_place()
 
434
        branch.unlock()
 
435
        return SuccessfulSmartServerResponse(('ok',))
 
436
 
 
437
 
 
438
class SmartServerBranchRequestGetPhysicalLockStatus(SmartServerBranchRequest):
 
439
    """Get the physical lock status for a branch.
 
440
 
 
441
    New in 2.5.
 
442
    """
 
443
 
 
444
    def do_with_branch(self, branch):
 
445
        if branch.get_physical_lock_status():
 
446
            return SuccessfulSmartServerResponse(('yes',))
 
447
        else:
 
448
            return SuccessfulSmartServerResponse(('no',))