~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/branch.py

  • Committer: Gordon Tyler
  • Date: 2012-02-28 04:58:14 UTC
  • mto: (6437.23.20 2.5)
  • mto: This revision was merged to the branch mainline in revision 6493.
  • Revision ID: gordon@doxxx.net-20120228045814-nab581dyd7ie5u14
Backport of fix for bug 939605 to bzr 2.5 series.

Show diffs side-by-side

added added

removed removed

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